1, eMule源代碼學(xué)習(xí)心得(1):eMule代碼的總體風(fēng)格和其它相關(guān)工程
eMule的官方首頁上寫著:2002年05月13日 一個叫做 Merkur 的人,他不滿意原始eDonkey2000客戶端并且堅信他能夠做的更好,所以他開始制作。他聚集了其它開發(fā)人員在他的周圍,并且eMule工程就此誕生。
eMule是一個典型的MFC程序,它的圖形界面等,已經(jīng)和MFC緊緊融合到了一起。因此通常情況下它只能在windows平臺下運行。有一些其它的工程,如aMule等,把它進行了移植,因此跨平臺的功能要強些。
其
實還有另外一個叫做xMule的工程,不過現(xiàn)在已經(jīng)人氣快不行了。在aMule的主頁上可以看到eMule移植到linux平臺下的一些歷史,最早是有個
叫做lMule的工程,他使用wxwidgets來進行eMule的跨平臺的移植,這個工程2003年就不再更新了,后來轉(zhuǎn)變成為xMule工程,它一度
是linux平臺下eMule的事實上的替代品。但是他們的程序員之間由于理念不同,發(fā)生了內(nèi)訌,導(dǎo)致aMule分裂出來,他們后來矛盾嚴(yán)重的時候曾經(jīng)一
度從理念問題上升到互相對對方進行人身攻擊,并且曾經(jīng)對對方的網(wǎng)站發(fā)動過DDos。后來aMule和xMule就是兩個完全不同的工程,xMule現(xiàn)在只
有HopeSeekr一個人在維護,基本上也沒有什么更新了。這一點不僅讓人感慨。今年寒假的時候我曾經(jīng)和HopeSeekr進行過一些交流,感覺他非常
自信,經(jīng)常拿著aMule的一部分代碼來給我看,說你看看他們的代碼這么這么寫,這簡直就是一陀xx嘛,這種代碼在某些情況下肯定會Crash掉嘛,相
反,你看看我們xMule的代碼,這里是這樣這樣,肯定就不會有這種問題了。
eMule從0.42版開始支持Kad技術(shù),這是一個非常重要
的里程碑。Kad是一種DHT的協(xié)議,它可以使節(jié)點之間互相保留一些其它節(jié)點的聯(lián)系信息,并且利用這樣一個“關(guān)系網(wǎng)”尋找到整個網(wǎng)絡(luò)中的任何一個節(jié)點以及
上面的資源,整個過程不需要任何中心服務(wù)器。因此向當(dāng)年搞napster那樣直接端掉中心服務(wù)器就搞跨napster網(wǎng)絡(luò)一樣來對付eMule的Kad網(wǎng)
就毫無作用了。0.42版是2004年2月27日放出的,比eDonkey2000的OverNet晚了將近一年,但是它的Kad網(wǎng)絡(luò)的規(guī)模卻在迅速擴
大。Overnet和eMule中的Kad使用的都是Kademlia結(jié)構(gòu),但是具體的消息報文的格式有區(qū)別,因此兩個DHT網(wǎng)絡(luò)并不能互相兼容。
OverNet直到現(xiàn)在,用戶也仍然維持在十萬左右,這是比較近的一篇文章寫的。但是eMule的Kad網(wǎng)的規(guī)模有多大,目前卻沒有一個很準(zhǔn)確的說法。由
此也可以看出開源軟件的力量。目前aMule的Kad網(wǎng)和eMule的Kad網(wǎng)是兼容的,xMule中還沒有Kad的支持。Kad協(xié)議的原始paper可
以在我們實驗室的機器上下到:
http://bigpc.net.pku.edu.cn:8080/paper/new/by%20conference/IPTPS/IPTPS02/Kademlia%20A%20Peer-to-Peer%20Information%20System%20Based%20on%20the%20XOR%20Metric%28IPTPS02%29.pdf
2. eMule源代碼學(xué)習(xí)心得(2):從emule.cpp開始,順便談如何編譯emule
先說一聲抱歉,因為前兩天剛回到家中,休息了一下,所以這兩天沒有更新。今天繼續(xù)昨天的話題。
eMule
的代碼結(jié)構(gòu)非常合理。雖然代碼量比較大,但是各個功能模塊之間的劃分都很合理。從它的工程文件里面就可以看出這一點。eMule把表示功能的代碼文件和表
示界面的代碼文件分開了,Source Files和Header Files是實現(xiàn)功能的代碼的源文件和頭文件,而Interface
Source和Interface
Header是實現(xiàn)圖形界面的源文件和頭文件。由于eMule的代碼量太大,本系列將跳過圖形界面的實現(xiàn),著重分析eMule的功能實現(xiàn)部分的代碼,而且
功能實現(xiàn)方面也主要挑主要的部分分析。本節(jié)將從emule.cpp開始分析,引出eMule中使用到的幾個主要的功能實現(xiàn)的類,并大體描述它們的作用。最
后介紹一下如何在VS2003下編譯eMule。emule中還有不少模塊實現(xiàn)的功能挺有用,我說的是在其它的程序里很有用,可以考慮在其它程序中進行復(fù)
用。
emule.cpp為類CemuleApp的實現(xiàn)。因此在運行時,首先會運行InitInstance進行一些初始化的工作。從這個函數(shù)里面我們也可以第一次看出那些即將在整個程序中發(fā)揮作用的類了。
最
開始的時候是計算出程序常用的一些目錄,如配置文件,日志文件等。接下來是ProcessCommandline,它的作用有兩方面,第一是確認該
eMule的運行方式,即命令行后面有沒有參數(shù),第二是確認目前eMule是不是只有一個實例在運行。在一般的情況下,雙擊eMule可執(zhí)行文件是不會帶
參數(shù)的。但是通過點擊鏈接或者打開關(guān)聯(lián)文件的方式打開eMule則相當(dāng)于帶參數(shù)運行eMule。通過在注冊表里添加一些項目可以讓一個程序和某種鏈接或者
某個后綴的文件產(chǎn)生關(guān)聯(lián)。具體辦法可以參見OtherFunctions.cpp中的Ask4RegFix,BackupReg,RevertReg三個
函數(shù)的功能。ProcessCommandline中通過創(chuàng)建帶有名稱的互斥信號量來確認是否有其它的eMule實例在運行。對于一個確定的名稱,
CreateMutex只能創(chuàng)建一個互斥信號量。因此通過該信號量是否創(chuàng)建成功就可以知道是否有其它eMule實例運行。如果有的話,而且又是帶參數(shù)的那
種模式,那么直接把這個參數(shù)使用Windows的消息機制發(fā)給那個窗口即可,接下來的代碼無非就是如何找到另外一個叫"eMule"的家伙以及給它發(fā)個什
么消息。pstrPendingLink是一個全局變量,表示將要被處理的命令行參數(shù)。它將會在初始化完成后一段時間后被處理。
下面兩個比
較重要的類是CPreferences和CStatistics。前者掌握著程序的大部分配置數(shù)據(jù),后者則進行各種統(tǒng)計。它們的特點都是有很多的成員變
量,而且還是靜態(tài)的,這種方式可以保證它們的唯一性,而且把這些變量統(tǒng)一到一個類管理。但是實際上并不需要了解每個變量的含義。thePrefs和
theStats是這兩個類的唯一的實例。
在處理完其它一些事情,包括創(chuàng)建圖形界面對象CemuleDlg后,接下來可以看到一排一排的創(chuàng)建新對象的語句。這些類將會實現(xiàn)eMule程序運行的主要功能,后面的系列將詳細分析。
最
后描述一下如何在VS2003里編譯eMule,由于eMule中用到了一些其它的庫,因此官方在提供eMule的源代碼下載的同時如果也提供這些庫的下
載會使源碼包變得很大。因此eMule選擇了讓開發(fā)者去那些庫的官方網(wǎng)站下載它們的方式。一般來說,編譯這種工程文件,很重要的地方是要保持各個庫和主程
序編譯參數(shù)的一致性。這些編譯參數(shù)中,最主要的參數(shù)有三個,字符集(多字節(jié)/Unicode),調(diào)試/發(fā)行,單線程/多線程,這樣排列組合一下就有八個版
本了。因此編譯的時候如果不注意,就會出現(xiàn)和程序設(shè)計無關(guān)的錯誤。
eMule0.47a解壓后自帶id3lib庫,還需要下載以下的庫:
zlib:
http://www.gzip.org/zlib/
ResizableLib:
http://sourceforge.net/projects/resizablelib/
Crypto++:
http://www.eskimo.com/~weidai/cryptlib.html
pnglib:
http://www.libpng.org/pub/png/libpng.html
下
載它們,解壓它們,編譯它們。編譯的時候注意要和eMule的工程的參數(shù)一致:字符集為Unicode,支持多線程安全,調(diào)試版或者是發(fā)行版可以根據(jù)需要
選擇,但是也要和eMule的工程參數(shù)一致。這些庫的解壓包里通常都能找到VC的工程文件,但是版本低一些,直接轉(zhuǎn)化就可以了。另外建議編譯這些庫的時候
都選擇生成靜態(tài)庫,不要生成動態(tài)的庫,這樣最后生成的可執(zhí)行文件就可以自己運行了。編譯完這些庫,包括從其它地方下載的和eMule自帶的id3lib庫
和CxImage庫后,就可以開始編譯emule了。而編譯emule也無非就是注意讓它能夠在編譯的時候找到所有的頭文件,以及在鏈接的時候能夠找到所
有的庫。在鏈接的時候能夠找到所有的庫可以通過修改工程文件里面的屬性 ->配置屬性 ->鏈接器
->輸入->附加依賴項來完成。但是能夠找到所有的頭文件反而需要一些技巧了。由于emule的代碼中對于這些庫的頭文件的包含,在一定程度
上限定了那些庫的路徑和emule的工程的路徑的相對位置,因此需要把那些解壓過的庫的目錄移到一些合適的地方,有時還需要給這些目錄改個名稱。
3. eMule源代碼學(xué)習(xí)心得(3):emule中最重要的幾個基礎(chǔ)設(shè)施
eMule
中要讀取的配置文件數(shù)量較多,每種配置文件都是自己定義的格式,為了方便讀取和存儲這些文件,eMule中有一個很重要的基礎(chǔ)設(shè)施類來復(fù)制這些文件操作,
它能夠很方便得處理一些常用數(shù)據(jù)類型的讀寫,并且?guī)в幸欢ǖ陌踩Wo機制。這項基礎(chǔ)設(shè)施在SafeFile.cpp和SafeFile.h中實現(xiàn)。在
kademlia\io目錄下有這項功能的另外一項實現(xiàn)。它們實現(xiàn)的功能基本上相似,但是kademlia\io目錄下的版本實現(xiàn)的時候多了一個以Tag
作為單位進行讀寫的功能。這些實現(xiàn)中和另外一項基礎(chǔ)設(shè)施,那就是字符串轉(zhuǎn)化密切相關(guān)。StringConversion.cpp和
StringConversion.h是eMule中專門復(fù)制各類字符串轉(zhuǎn)化的基礎(chǔ)設(shè)施,什么Unicode啊,多字節(jié)流啊,或者是UTF-8之類的,在
這里轉(zhuǎn)化全部都不是問題。關(guān)于字符串轉(zhuǎn)化,個人推薦盡量使用Unicode的寬字符,這樣可以最大程度得避免亂碼。
SafeFile.cpp
或者kademlia\io目錄下的實現(xiàn)都有這樣的特點,那就是把數(shù)據(jù)操作的行為和數(shù)據(jù)操作的對象分割開來。它們都定義了一個抽象的數(shù)據(jù)操作的基類(在
SafeFile.cpp中是CFileDataIO,在kademlia目錄下是DataIO.cpp實現(xiàn)的Kademlia::CDataIO),這
個類中只負責(zé)實現(xiàn)在邏輯上操作一項數(shù)據(jù)的行為,例如,要讀取出一個32位的整型,那么就是讀出四個字節(jié)到一個整型數(shù)值的地址中,要讀取或者寫入其它類型的
數(shù)據(jù)用的是類似的方法。但是這個類把物理上進行數(shù)據(jù)操作的方法全部都聲明為純虛函數(shù),即讀出多少個字節(jié),寫入多少個字節(jié)這樣的。有了這樣一個基類,就可以
非常方便得在它上面進行重載,把這些純虛函數(shù)定義為向某塊內(nèi)存中進行讀寫的操作,就能很方便得將較為復(fù)雜的數(shù)據(jù)序列化到一塊連續(xù)的內(nèi)存,而如果這些純虛函
數(shù)是向文件讀寫的操作,那么自然就可以很方便得用來讀寫各種格式比較奇怪的自己定義的配置文件了。
這些類要讀取的數(shù)據(jù)對象通常有這些,各種
整型,字符串,以及Tag類型。整型讀寫起來比較簡單,從1個字節(jié)的,2個字節(jié)的到4個,8個或者16個字節(jié)類型的數(shù)據(jù)讀寫方法都比較類似。這里要稍微提
一下16個字節(jié)的那種,16個字節(jié)是128位,是eMule中的Kad網(wǎng)的隨機生成的ID的長度,也是eMule中常用的MD4的hash算法生成的結(jié)果
的長度。通常用來直接存取整個的這樣的一個ID。在kademlia\utils目錄下的UInt128.cpp實現(xiàn)了一個表示128位的整數(shù)的類,功能
十分完善,可以進行一些算術(shù)操作,并且可以進行比較,這樣為它以后作為key出現(xiàn)在hash表中打下了基礎(chǔ)。仔細學(xué)習(xí)UInt128.cpp中的代碼實現(xiàn)
可以學(xué)到很多在編寫這種自定義的數(shù)據(jù)對象類型時應(yīng)該注意的問題。
數(shù)據(jù)操作中另外一項很重要的操作是字符串,總的原則是先寫一個長度,再寫內(nèi)
容。但是到具體的操作的時候就需要注意這些細節(jié)了,如長度是寫4個字節(jié)還是兩個字節(jié),字符串的內(nèi)容要不要用UTF-8進行編碼。這些操作就需要和
StringConversion.cpp緊密合作了。其實后者的字符串轉(zhuǎn)化函數(shù)很多也是調(diào)用ATL的相關(guān)函數(shù),只是在外面再包上一層MFC的
CString。
在kademlia\io\DataIO.cpp中實現(xiàn)的CDataIO中,還另外實現(xiàn)了按照Tag進行讀寫的功能。這在
網(wǎng)絡(luò)上交換共享文件的元信息非常重要,通常一個文件的元信息就可以分解成很多的Tag,如"文件名=xxx","文件長度=xxx"等等。也就是說,一個
Tag就是表示某項屬性等于某個值這樣一個事實。在Opcodes.h這個文件中定義了很多的代碼,其中就有很多常見的Tag的屬性名稱。CDataIO
類中存儲Tag的屬性名都是先存一個字節(jié)的類型,再存名稱,最后按照類型存值。
eMule中的這幾項基礎(chǔ)設(shè)施都是編寫得比較好的,可以很方
便得拿出來復(fù)用。像字符串編碼的處理和具有一定數(shù)據(jù)結(jié)構(gòu)的文件IO操作在很多地方都會很有用。eMule中這些類的實現(xiàn)基本上復(fù)制到其它的工程文件中只要
稍微修改一下很快就能使用。以后我們還將看到eMule中很多其它很有用的基礎(chǔ)設(shè)施。
4. eMule源代碼學(xué)習(xí)心得(4):對自己的資源要了如指掌,CKnownFileList類的作用
emule作為一個文件共享方面的程序,首先要對自己共享的所有的文件的信息都十分清楚,類CKnownFileList的作用就是這樣的,它在emule.cpp中隨著cmuleapp類創(chuàng)建的時候被創(chuàng)建。
CKnownFileList
類使用了MFC的CMap類來維護內(nèi)部的hash表,這也可以看出emule和MFC的關(guān)系確實非常緊密。這里如果用STL的map其實也是可以的。它內(nèi)
部維護了一個已知的文件的列表和取消了的文件列表。這些hash表的關(guān)鍵字都是文件的hash值。這樣能夠判斷出文件名不同而內(nèi)容相同的文件,而一般要讓
不同內(nèi)容的文件有相同的hash值是非常困難的,這也是hash函數(shù)它設(shè)計的初衷。因此除非是碰上王小云教授這樣的牛人,我們基本上可以認為,兩個文件
hash值相同就代表了它們內(nèi)容相同。再來看CKnownFileList.cpp,這個文件其實并不長,因為管理一個列表確實不需要太多種類的操作,如
果對于每個具體的文件有一個很強大的類來處理它的話。而這里確實有,它就是CKnownFile。有了這么一個類,我們就可以看到,
CKnownFileList類所需要做的工作就是能夠根據(jù)一些信息查找到對應(yīng)的CKnownFile類,能夠復(fù)制其它的列表中的信息,能夠把所有的這些
信息存成文件,然后下次emule運行的時候能夠把這些信息快速恢復(fù)出來,最重要的是能夠在完成以上工作的情況下不造成內(nèi)存泄漏。
CKnownFile
類就是一個專門關(guān)注某個特定文件的信息的類,它仍然有其基類CAbstractFile。但是它和CAbstractFile類的主要區(qū)別就是
CAbstractFile類只有基本的信息存取的功能,而CKnownFile能夠主動的生成這些信息,例如,給一個文件的路徑給
CKnownFile,它能夠主動地去獲取和這個文件有關(guān)的一切信息,并且把它保存在自己的成員變量里(CreateFromFile)。
CKnownFile.cpp文件看上去比較長,是因為它做的工作比較多,現(xiàn)在版本的emule中,除了對某個文件進行全文hash以外,還采用了BT的
方式,進行分塊hash,這樣在傳輸文件的時候,即使發(fā)生出錯的情況,也可以不必重傳整個文件,而只是重傳有錯誤的那塊,這種機制叫做高級智能損壞處理
(AICH,Advanced Intelligent Corruption Handling),這個機制以后再繼續(xù)分析。
CKnownFile
把讀到的文件信息都保存成一個一個的Tag。它在運行中會盡量得獲取更多的文件信息,例如,對于媒體類型的文件,它能夠調(diào)用id3lib庫來獲取諸如作
者,唱片發(fā)行年代,風(fēng)格等tag信息。如果是視頻媒體文件,它還會去抓圖(功能實現(xiàn):CFrameGrabThread)。
CKnownFile
還能夠隨時掌握目前該文件的下載情況(內(nèi)部有個CUpDownClient的列表),當(dāng)然,還會根據(jù)要求序列化和反序列化自己,LoadFromFile
和WriteToFile都以CFileDataIO為參數(shù),這樣方便CKnownFileList保存和讀取它的列表中的所有文件的信息。
5. eMule源代碼學(xué)習(xí)心得(5):分塊機制--正確傳輸資源的保證
為
了加快內(nèi)容分發(fā)的速度,分塊處理是一種簡單有效的方法。emule中對每個文件都進行了分塊處理。另外分塊還有一個好處就是如果保留了每一分塊的hash
值,就能在只下載到文件的一部分時判斷出下載內(nèi)容的有效性。emule在獲取每個共享文件的信息時,就對它進行了分塊處理,因此如果要知道emule中的
分塊處理和恢復(fù)機制,看CKnownFile::CreateFromFile函數(shù)的實現(xiàn)就行了。
這個函數(shù)中牽涉到的和分塊處理以及hash計算相關(guān)的類都在SHAHashSet.cpp和SHAHashSet.h中。下面介紹其中幾個主要的類:
CAICHHash
類只負責(zé)一塊hash值,提供兩個CAICHHash類之間的直接賦值,比較等基本操作。CAICHHashAlgo是一個hash算法的通用的接口,其
它hash算法只要實現(xiàn)這種接口都能使用,這樣,可以很方便得使用不同的hash算法來計算hash值。CAICHHashTree則是一個樹狀的
hash值組織方式,它有一個左子樹和右子樹成員變量,類型是指向CAICHHashTree的指針,這是一個典型的實現(xiàn)樹狀結(jié)構(gòu)的方法。
CAICHHashSet中包含了一個CAICHHashTree類型的變量,它直接向CKnownFile負責(zé),代表的是一個文件的分塊信息。
SHAHashSet.h
文件的開始的注釋部分向我們解釋了它的分塊的方式。這里要用到兩個常量9728000和184320,它們分別是9500k和180k。這是emule中
兩種不同粒度的分塊方式,即首先把一個很大的文件分割成若干個9500k的塊,把這些塊組織成一顆樹狀的結(jié)構(gòu),然后每一個這樣的塊又分解成若干個180k
的塊(52塊,再加一個140k的塊),仍然按照樹狀的結(jié)構(gòu)組織起來。最后總的結(jié)構(gòu)還是一顆樹。
CKnownFile::CreateFromFile
方法是在讀取目標(biāo)文件的內(nèi)容時,逐步建立起這樣一顆樹的。CAICHHashTree::FindHash能夠根據(jù)讀取到的目標(biāo)文件的偏移量和下一塊的大
小,來找出對應(yīng)的樹枝節(jié)點(就是一個指向CAICHHashTree的指針)。如果有必要的話,還會自動創(chuàng)建這些樹枝節(jié)點。因此在進行分塊操作的時候,把
文件從頭到尾讀一邊,整個CAICHHashTree就建立起來了,對應(yīng)的分塊hash值也賦值好了。最后我們還需要注意的就是CKnownFile類中
的hashlist變量。就是說它還單獨保留直接以9728000字節(jié)為單位的所有分塊的MD4算法的hash值。這樣對于一個文件就有了兩套分塊驗證的
機制,能夠適應(yīng)不同場合。
6. eMule源代碼學(xué)習(xí)心得(6):網(wǎng)絡(luò)基礎(chǔ)設(shè)施--網(wǎng)絡(luò)基礎(chǔ)設(shè)施的基礎(chǔ)設(shè)施
MFC中已經(jīng)有一些網(wǎng)絡(luò)基礎(chǔ)設(shè)施類,如CAsyncSocket等。但是emule在設(shè)計中,為了能夠更加高效得開發(fā)網(wǎng)絡(luò)相關(guān)的代碼,構(gòu)建了另外的一些類作為基礎(chǔ)設(shè)施,這些基礎(chǔ)設(shè)施類的代碼也有很高的復(fù)用價值。
首
先是CAsyncSocketEx類。AsyncSocketEx.h中對這個類的特點已經(jīng)給出了一定的說明。它完全兼容CAsyncSocket類,即
把應(yīng)用程序中所以的CAsyncSocket換成CAsyncSocketEx,程序仍然能夠和原來的功能相同,因此在使用上更加方便。但是在這個基礎(chǔ)
上,它的效率更高,主要是在消息分發(fā)機制上,即它處理和SOCKET相關(guān)的消息的效率要比原始的MFC的CAsyncSocket類更高。另外,
CAsyncSocketEx類支持通過實現(xiàn)CAsyncSocketExLayer類的方式,將一個SOCKET分成若干個層,從而可以很方便得實現(xiàn)許
多網(wǎng)絡(luò)功能,如設(shè)置代理,或者是使用SSL進行加密等。
另外還有ThrottledSocket.h中定義的
ThrottledControlSocket類和ThrottledFileSocket類,這兩個類只定義了兩個接口。任何其它的網(wǎng)絡(luò)套接字類如果想
實現(xiàn)限速的功能,只需要在其默認的發(fā)送函數(shù)(如Send或Sendto)中不發(fā)送數(shù)據(jù)而是把數(shù)據(jù)緩存起來,然后在實現(xiàn)
ThrottledControlSocket或者ThrottledFileSocket接口中的SendFileAndControlData或
SendControlData方法時才真正把數(shù)據(jù)發(fā)送出去,這樣就能實現(xiàn)上傳限速,而這也是需要UploadBandwidthThrottler類進
行配合,UploadBandwidthThrottler是一個WinThread的子類,平時單獨運行一個線程。下一次會詳細描述它是如何控制全局的
上傳速度的。
7. eMule源代碼學(xué)習(xí)心得(7):網(wǎng)絡(luò)基礎(chǔ)設(shè)施--全局限速器UploadBandwidthThrottler
UploadBandwidthThrottler
是emule中使用的全局的上傳限速器。它繼承了CWinThread類,且在該類被創(chuàng)建的時候,就新創(chuàng)建一個線程開始單獨運行。在該類被析構(gòu)時也會自動
停止相應(yīng)的線程。這個線程的目標(biāo)函數(shù)就是RunProc,然后為了避免在RunProc函數(shù)不能使用this指針的情況,它使用了RunInternal
來實際完成工作線程的工作。在emule中,還有另外一個類LastCommonRouteFinder有類似的結(jié)構(gòu)。
UploadBandwidthThrottler
中保存了若干的套接字(Socket)隊列,這些隊列的處理方式略有不同。在標(biāo)準(zhǔn)隊列(m_StandardOrder_list)里面排隊的都是實現(xiàn)了
ThrottledFileSocket接口的類,通常這些類能夠傳輸文件內(nèi)容也可以傳輸控制信息。而其它四個隊列都是實現(xiàn)
ThrottledControlSocket接口的類的隊列,在這些隊列中的類主要以傳輸控制信息為主。這四個隊列為臨時高優(yōu)先級,臨時普通優(yōu)先級,正
式高優(yōu)先級,正式普通優(yōu)先級。和把套件字直接添加到普通隊列(AddToStandardList)不同,
QueueForSendingControlPacket把要添加到隊列的套接字全部添加到兩個臨時隊列。根據(jù)它們的優(yōu)先級添加到普通的臨時隊列。在
RunInternal的大循環(huán)中,臨時隊列中的項目先被移到普通隊列中,然后再進行處理。
UploadBandwidthThrottler
使用了兩個臨界區(qū),兩個事件。pauseEvent是用來暫停整個大循環(huán)的動作的。而threadEndedEvent是標(biāo)志整個線程停止的事件。
sendLocker是大循環(huán)中使用的主要的臨界區(qū),而tempQueueLocker是為兩個臨時隊列額外添加的鎖,這樣可以一邊發(fā)送已有隊列中的套界
字要發(fā)送的數(shù)據(jù),一邊把新的套接字加到隊列中。
UploadBandwidthThrottler的RunInternal中的大循環(huán)是該
工作線程的日常操作。這個大循環(huán)中做了以下事情,計算本次配額,即本次循環(huán)中能夠發(fā)送多少字節(jié),好安排調(diào)度,計算本次循環(huán)應(yīng)該睡眠多少時間,然后進行相應(yīng)
的睡眠,從而進行限速。操作控制信息隊列,發(fā)送該隊列中的數(shù)據(jù),注意,控制隊列中的套接字(m_ControlQueueFirst_list和
m_ControlQueue_list)只使用一次就離開隊列。而標(biāo)準(zhǔn)隊列中的套接字不會這樣。在一輪循環(huán)結(jié)束后,如果還有沒有用完的發(fā)送數(shù)據(jù)的配額,
則會有部分配額保存到下一輪。
8. eMule源代碼學(xué)習(xí)心得(8):網(wǎng)絡(luò)基礎(chǔ)設(shè)施--emule套接字CEMSocket
CEMSocket
是CAsyncSocketEx和ThrottledFileSocket的子類,它把若干功能整合到了一起,因此可以作為emule使用起來比較方便的
套接字。例如它可以很方便得指定代理,把CAsyncSocketEx中的創(chuàng)建一個新的代理層并且添加到列表中的功能對外屏蔽了。另外它可以分出狀態(tài),如
當(dāng)前是否在發(fā)送控制信息等。
CEMSocket中我們需要仔細考察的是它的SendControlData和
SendFileAndControlData方法。如前所述,這些方法是用來和UploadBandwidthThrottler進行配合,以便完成全
局的限速功能的。它的功能應(yīng)該是按照UploadBandwidthThrottler的要求,在本次輪到它發(fā)送數(shù)據(jù)時發(fā)送指定數(shù)量的字節(jié)數(shù)。因此,應(yīng)用
程序的其它部分在使用CEMSocket時,如果要達到上傳數(shù)據(jù)限速的目的,不應(yīng)該直接調(diào)用標(biāo)準(zhǔn)的Send或者SendTo方法,而是調(diào)用
SendPacket。這里就有了另外一個結(jié)構(gòu)Packet,它通常包含一個emule協(xié)議中完整的包,例如有協(xié)議的頭部數(shù)據(jù)等,還內(nèi)置了
PackPacket和UnPackPacket方法,可以自行進行壓縮和解壓的功能。SendPacket把要發(fā)送的Packet放到自己的隊列中,這
個隊列也有兩個,控制信息包隊列,和標(biāo)準(zhǔn)信息包隊列。如果有必要,把自己加入到UploadBandwidthThrottler的隊列中。
我
們注意到CEMSocket的SendControlData和SendFileAndControlData方法其實都是調(diào)用自己的另一個重載的
Send方法。而且我們也已經(jīng)知道這個方法是在UploadBandwidthThrottler的工作線程中的大循環(huán)中被調(diào)用的,而這個Send方法的
內(nèi)容本身也是一個大循環(huán),但是意義很明了,就是在不超過自己本次發(fā)送的配額的情況下,把自己的包隊列中的包取出來,并且發(fā)出去。同樣,這里也用到了一個臨
界區(qū),它是為了保證從包隊列中取出包來發(fā)送和把包往隊列中放的操作是互斥的。因此,如果把它和UploadBandwidthThrottler結(jié)合起
來,我們就看到了一個兩層的隊列,即所有的套接字組成了一個發(fā)送隊列,在UploadBandwidthThrottler的控制下保證了對速度的限制,
而每個套接字即將發(fā)送的數(shù)據(jù)包又組成了一個隊列,保證了每次進行數(shù)據(jù)發(fā)送的時候都會滿足UploadBandwidthThrottler的要求。
9. eMule源代碼學(xué)習(xí)心得(9):搜索信息集-CSearchList
CSearchList
是emule中的搜索列表,掌管emule中所有的搜索請求。CSearchFile是這個列表中的元素,代表了一次搜索的相關(guān)信息。它們的關(guān)系和之前描
述的已知文件和已知文件列表有一些類似的地方。CSearchList的主要任務(wù)就是對其一個叫做list的類型為CSearchFile列表的內(nèi)部變量
進行維護,提供很方便得往這個列表中添加,刪除,查詢,變更等操作的接口。另外,每一個搜索都有一個ID,是一個32位的整數(shù)。CSearchList中
記錄了每個搜索目前搜到的文件個數(shù)和源的個數(shù)(m_foundFilesCount和m_foundSourcesCount)。
CSearchFile
是CAbstractFile的另一個子類(CKnownFile也是),它保存了某個文件和搜索相關(guān)的信息,而不是這個文件本身的信息(這些信息在
CAbstractFile中已經(jīng)包括了),這些和搜索有關(guān)的信息就是都在哪些機器上有這個文件,以及哪個服務(wù)器上搜到的這個文件。甚至還可以向搜索文件
添加預(yù)覽。在這個類的定義中嵌套定義了兩個簡單的結(jié)構(gòu)SServer和SClient,表示了該搜索文件的可能來源,服務(wù)器或者其它客戶端。
m_aClients和m_aServers是這兩個簡單結(jié)構(gòu)的一個數(shù)組,CSearchFile自然也提供了對這個數(shù)組的操作的接口,方便
CSearchList使用。
CSearchList對外提供了搜索表達的接口,即每當(dāng)有一個新的搜索提交時CSearchList::
NewSearch會建立一個新的搜索項,但是此時還沒有任何對應(yīng)的搜索文件,因此只是在文件個數(shù)和搜索ID的對應(yīng)表
(m_foundFilesCount和m_foundSourcesCount)中建立新的項目。另外當(dāng)有搜索結(jié)果返回時
ProcessSearchAnswer或ProcessUDPSearchAnswer能夠?qū)Ψ祷氐陌苯幼鎏幚恚瑒?chuàng)建相應(yīng)的搜索文件信息
CSearchFile對象,并加入到自己的列表中。當(dāng)然,要把重復(fù)的搜索結(jié)果去除,發(fā)現(xiàn)同一個hash的文件的多個源時也會給它們建立一個二級列表
(CSearchFile::m_list_parent)?,F(xiàn)在我們可以看出,CSearchList只負責(zé)和搜索有關(guān)的信息的儲存和讀取,本身并不進
行搜索。
10. eMule源代碼學(xué)習(xí)心得(10):服務(wù)器信息集-CServerList
嗯,明天要回北京了,今天打算好好休息一下,故挑了個內(nèi)容少的來說,呵呵。
盡
管目前有了Kad網(wǎng)絡(luò),但是使用服務(wù)器來獲取各個emule用戶的共享文件列表仍然是emule中主要的資源獲取方式。CServerList就是
emule中負責(zé)管理服務(wù)器列表的類。和前面若干列表類結(jié)構(gòu)類似,CServerList需要對外提供列表的增加,刪除,查找,修改等接口。在
CServerList中,每個服務(wù)器的信息是一個CServer類。和搜索信息不一樣,但是和已知文件列表一樣,服務(wù)器的信息列表是需要長期保留的,因
此CServerList和CKnownFileList類一樣提供了把它所包含的所有信息保存到一個文件中,以及從這個文件中讀回其信息的功能。
CServer
中的結(jié)構(gòu)比較簡單,只需要保留服務(wù)器的各種信息即可。它可以通過IP地址和端口來創(chuàng)建,也可以通過一個簡單的結(jié)構(gòu)ServerMet_Struct來創(chuàng)
建,其中后者是用來直接從文件中讀取的。該結(jié)構(gòu)僅僅包含IP地址和端口以及屬性的個數(shù),CServer中其它的屬性在保存到文件中時,均采用Tag方式保
存。
CServerList除了提供通常的CServer信息外,還提供一些統(tǒng)計信息諸如所有的服務(wù)器的用戶數(shù),共享的文件數(shù)等。這些統(tǒng)計信息也是基于每個單獨的CServer的相關(guān)信息計算出來的。
11. eMule源代碼學(xué)習(xí)心得(11):emule的通信協(xié)議-一些基本的約定
接
下來將不可避免得要碰到emule的協(xié)議。emule的通信協(xié)議格式設(shè)計成一種便于擴充的格式。對于TCP連接來說,連接中的數(shù)據(jù)流都能夠劃分成為一個一
個的Packet,CEMSocket類中就完成了把接收到的數(shù)據(jù)劃分成Packet這一工作。但是具體的對于每個Packet進行處理的工作被轉(zhuǎn)移到它
的子類中進行。CEMSocket類的兩個子類CServerSocket和CClientReqSocket所代表的TCP連接就分別是客戶端和服務(wù)器
之間的TCP連接以及客戶端之間的TCP連接。在數(shù)據(jù)流中的第一個字節(jié)代表的是通信的協(xié)議簇代碼,如0xE3為標(biāo)準(zhǔn)的edonkey協(xié)議,0xE4為
kademlia協(xié)議等等。接下來的四個字節(jié)代表包內(nèi)容的長度,所有的包都用這種方式發(fā)送到TCP流中,就可以區(qū)分出來了。另外每個包內(nèi)容中的第一個字節(jié)
為opcode,即在確定了某個具體協(xié)議后,這個opcode確定了這個包的具體含義。
對于走UDP協(xié)議的包,處理起來更加得簡
單,因為UDP本來就是以一個包一個包作為單位在網(wǎng)絡(luò)上流傳的,因此不需要在包的內(nèi)容中再包含表示長度的字段。每個UDP包的第一個字節(jié)是協(xié)議簇代碼,其
它內(nèi)容就是包的內(nèi)容。CClientUDPSocket類負責(zé)處理客戶端和客戶端之間的UDP包,而CUDPSocket類負責(zé)處理客戶端和服務(wù)器之間的
UDP包。另外還有個Kademlia::CKademliaUDPListener類,專門處理和Kademlia協(xié)議相關(guān)的UDP包。
最
后說一下Packet類,這個類以前只是提到過。它是emule的通信協(xié)議的最小單位。我們可以看出,它的構(gòu)造函數(shù)有多個版本,這也是為了可以用不同的方
式來創(chuàng)建Packet。例如只包含一個頭部信息的緩沖區(qū),或者只是指定協(xié)議簇代碼等。而且它內(nèi)部實現(xiàn)了壓縮和解壓的方法,該方法直接調(diào)用zlib庫中的壓
縮方法,可以減少數(shù)據(jù)的傳輸量。這里要注意一點的就是壓縮的時候協(xié)議簇代碼是不參與壓縮的,壓縮完畢后會更換協(xié)議簇代碼,例如代碼為標(biāo)準(zhǔn)edonkey協(xié)
議0xE3的包在壓縮后,協(xié)議代碼就變成0xD4了,這里進行協(xié)議代碼變化是為了使接受方能夠正確識別并且進行相應(yīng)的解壓操作。
12. eMule源代碼學(xué)習(xí)心得(12):emule的通信協(xié)議-客戶端和服務(wù)器之間的通信概述
客
戶端和服務(wù)器之間的所有通信由類CServerConnect掌握。CServerConnect本身不是套接字的子類,但是它的成員變量
CServerSocket類型的connectedsocket是。CServerConnect內(nèi)部有一列表,可以保存若干
CServerSocket類型的指針。但是這并不說明它平時連接到很多服務(wù)器上。它只是可以同時試圖連接到若干個服務(wù)器上,這只是因為連接到服務(wù)器上的
行為不一定能成功。
CServerSocket類是CEMSocket的子類,它比CEMSocket要多保存一些狀態(tài),比如當(dāng)前的服務(wù)器
連接狀態(tài)。它同時還保留它當(dāng)前所連接的服務(wù)器的信息。通過分析CServerSocket::ProcessPacket就可以直接把emule客戶端和
服務(wù)器之間的通信協(xié)議理解清楚,這里是服務(wù)器發(fā)回的包。TCP連接建立后的第一個包是在CServerConnect::
ConnectionEstablished中發(fā)出的,即向服務(wù)器發(fā)出登陸信息。如果登陸成功,則能夠從服務(wù)器處獲取自己的ID,這是一個32位的長整
數(shù)。如果這個數(shù)小于16777216,那么我們稱它為LowID。具有LowID的客戶端通常情況下其它客戶端將不能直接連接它。得到LowID的原因比
較多,例如當(dāng)自己處于NAT的后端的時候。獲取自己的ID后將會向服務(wù)器發(fā)送自己的共享文件列表,這一動作由共享文件列表類
CSharedFileList來完成。
其它類型包沒有必要全部都列出來,以后可以通過在分析其它部分時,因為牽涉到往服務(wù)器發(fā)送或者接受數(shù)據(jù)的時候再進行相應(yīng)的分析。
13. eMule源代碼學(xué)習(xí)心得(13):emule的通信協(xié)議-客戶端和客戶端之間的通信概述
客
戶端和客戶端之間的TCP通信由CListenSocket和CClientReqSocket完成。這也是提供網(wǎng)絡(luò)服務(wù)的應(yīng)用程序的典型寫法。其中
CListenSocket只是CAsyncSocketEx的子類,只負責(zé)監(jiān)聽某個TCP端口。它只是內(nèi)部有一個CClientReqSocket類的
列表。而CClientReqSocket是CEMSocket的子類,因此它能夠自動完成emule的packet識別工作。它有
ProcessPacket和ProcessExtPacket來處理客戶端和客戶端之間的包,其中前者是經(jīng)典的eDonkey協(xié)議的包,后者是
emule擴展協(xié)議的包。
CListenSocket和CClientReqSocket類之間的關(guān)系和前面分析的列表類和它對應(yīng)的成員類
的關(guān)系是相似的,CListenSocket提供對自身的CClientReqSocket列表中的元素的增加,查詢,刪除等操作。同時也維護關(guān)于這些成
員的一些統(tǒng)計信息。我們注意到CListenSocket在其構(gòu)造函數(shù)中就把自己添加到CListenSocket類
(theApp.listensocket,該類的唯一實際示例)的列表中。
CClientReqSocket類和
CUpDownClient類之間存在著對應(yīng)關(guān)系。它們都表示了另外一個客戶端的一些信息,但是CClientReqSocket類主要側(cè)重在網(wǎng)絡(luò)數(shù)據(jù)方
面,即負責(zé)兩邊的互相通信,而CUpDownClient類負責(zé)的是從邏輯上對網(wǎng)絡(luò)另一邊的一個客戶端進行表達。CUpDownClient類代碼很長,
以后再說。
14. eMule源代碼學(xué)習(xí)心得(14):emule中的信譽機制
信譽機制在P2P系統(tǒng)中有非常重要的作用。為
了使用戶更加愿意共享自己的資源,需要有一些機制能夠讓對整個P2P系統(tǒng)貢獻更大的用戶有更多的激勵。在emule中,激勵機制的設(shè)計方案是tit-
for-tat這種最直觀的方案。這種方案的意義就是最簡單的如果別人對你好,那么你也對別人好。
下面看實際的實現(xiàn)。
CClientCreditsList和CClientCredits類負責(zé)emule中的信譽機制。我們再次見到這種列表和元素之間的關(guān)系,不必再重復(fù)
那些語言。和信譽相關(guān)的信息是需要永久保存的,這樣才有意義,因此CClientCreditsList提供了LoadList和SaveList方法。
我們另外注意到,CClientCredits類可以使用CreditStruct結(jié)構(gòu)來創(chuàng)建,而CreditStruct結(jié)構(gòu)只包含靜態(tài)信息。主要是上
傳量和下載量等。
信譽機制的信息需要有一定的可靠性,在emule中采用了數(shù)字簽名的方式來做到這一點。Crypto++庫為emule全
程提供和數(shù)字簽名驗證相關(guān)的功能。CClientCreditsList在創(chuàng)建時,會裝載自己的公鑰私鑰,如果沒有的話,會創(chuàng)建一對。
CClientCreditsList中包含的有效的信息都是經(jīng)過其它人數(shù)字簽名的,所以更加有信服力。在實際使用中,這些信息和自己的私鑰要注意保存。
重裝emule后應(yīng)該把配置文件目錄先備份,這樣能夠保留自己辛辛苦苦積攢的信譽。
15. eMule源代碼學(xué)習(xí)心得(15):下載任務(wù)即部分文件的表示
CPartFile
類是emule中用來表示一個下載任務(wù)的類。從它的名字也可以看出來,這就是一個還沒有完成的文件。當(dāng)一個下載任務(wù)被創(chuàng)建時,emule會在下載目錄中創(chuàng)
建兩個文件,以三位數(shù)字加后綴part的文件,例如001.part,002.part等。還有一個以同樣的數(shù)字加上.part.met的文件,表示的是
對應(yīng)文件的元信息。part文件會創(chuàng)建得和原始文件大小一樣,當(dāng)下載完成后,文件名會修改成它本來的名稱。而事實上,諸如這個文件原來叫什么名稱,修改日
期等等信息都在對應(yīng)的.part.met元文件中。.part.met中還包含了該文件中那些部分已經(jīng)下載完成的信息。
CPartFile
類中Gap_Struct來表示文件的下載情況,一個Gap_Struct就是一個坑,它表示該文件從多少字節(jié)的偏移到多少字節(jié)偏移是一個坑。下載的過程
就是一個不斷填坑的過程。CPartFile類中有個成員變量gaplist就是該文件目前的坑的狀況列表。需要主要的是有時填了坑的中間部分后,會把一
個坑變成兩個坑。坑的列表也會被存進.part.met中。
CPartFile類的代碼很龐大,但是這是必須的。首先,它的創(chuàng)建就有幾種可
能,從搜索文件CSearchFile中創(chuàng)建,這種情況發(fā)生在用戶搜索到他想要的文件后點擊下載時發(fā)生。從一個包含了ed2k鏈接的字符串中創(chuàng)建,它會提
取出該ed2k鏈接中的信息,并用來創(chuàng)建CPartFile。剩下的一種,就是當(dāng)emule程序重啟后,恢復(fù)以前的下載任務(wù)。這時就是去下載目錄中尋找那
些.part和.met文件了。另外它還需要不斷得處理下載到的數(shù)據(jù),為了減少磁盤開銷,使用了Requested_Block_Struct結(jié)構(gòu)來暫存
寫入的數(shù)據(jù)。它內(nèi)部維護一個CUpDownClient的列表,如果知道了該文件的一個新的來源信息,就會創(chuàng)建一個對應(yīng)的CUpDownClient。后
者是emule中代碼量最大的類。它還要把它的狀態(tài)用彩色的條裝物顯示出來提供給GUI。
前面提到的AICH機制對于最大程度得保證下載文件的正確性以及盡量減少重復(fù)傳輸都有很大的幫助,在下載的過程中,該機制會經(jīng)常對下載到的數(shù)據(jù)進行校驗。
最
后提一下它的Process方法。該方法是emule中為了盡量減少線程的使用而采取的一種有一些類似于輪詢的機制。其它很多類中也有Process方
法,這個方法要做的事情就是在一些和日常運行有關(guān)的事情,例如檢查為了下載該文件而鏈接到自己的各個客戶端的狀態(tài),向它們發(fā)送下載請求等。
16. eMule源代碼學(xué)習(xí)心得(16):下載任務(wù)隊列
CDownloadQueue
是下載隊列類。這個隊列中的項目是CPartFile指針。因此和emule中出現(xiàn)的很多其它的列表類一樣,它需要能夠提供對這個列表中的元素進行增加,
查詢,刪除的功能。例如查詢的時候能夠根據(jù)該文件的hashID或者索引來進行查詢。CDownloadQueue同時還要完成一些統(tǒng)計工作。
和
其它的列表類不一樣的是,它的所有元素的信息并不是集中存放于一個文件,而是對應(yīng)于每一個下載任務(wù),單獨得存放在一個元信息文件(.part.met)
中,因此當(dāng)該類進行初始化的時候,它需要尋找所有可能的下載路徑,從那些路徑中找到所有的.part.met文件,并且試圖用這些文件來生成
CPartFile類,并且將這些通過.part.met文件正確生成的CPartFile類添加到自己的列表中,同樣,在退出時,所有的下載任務(wù)的元信
息也是自行保存,不會合成為一個文件。
CDownloadQueue中的Process方法的主要任務(wù)就是把它的列表中的
CPartFile類中的Process方法都調(diào)一遍,另外主要的一些關(guān)于下載情況的統(tǒng)計信息也是在每一輪的Process后進行更新的。從這里我們也可
以看出Process方法在emule中的意義,就是一個需要經(jīng)常執(zhí)行的方法,通過經(jīng)常執(zhí)行它們來完成日常工作,而且所有的這些Process方法肯定是
順序執(zhí)行,因此可以減少很多多線程的同步之類的問題。emule中已經(jīng)盡量減少了多線程的使用,但是在很多地方如果多線程是不可避免的話,也不會排斥。
17. eMule源代碼學(xué)習(xí)心得(17):上傳任務(wù)隊列
CUploadQueue是上傳隊列類。這個列表類中只有以
CUpDownClient為元素的列表,它和其它列表類還有一個很大的不同就是它所保存的信息都不需要持久化,即不需要在當(dāng)前的emule退出后還記住
自己正在給誰上傳文件,然后下次上線的時候再繼續(xù)給他們傳,這在大部分情況下是沒有意義的。
上傳隊列類列表中有兩個列表,上傳列表和排隊列表。當(dāng)一個收到一個新的下載請求后,它會把對應(yīng)的客戶端先添加到排隊列表中,以后再根據(jù)情況,把它們不斷添加到上傳列表中。在這里,信譽機制將會對此產(chǎn)生影響。
CUploadQueue的Process方法就相對簡單了,那就是向上傳隊列中的所有客戶端依次發(fā)送數(shù)據(jù),而排隊的客戶端是不會得到這個機會的。另外它還需要完成關(guān)于上傳方面的一些統(tǒng)計信息。
另
外我們還需要注意在CUploadQueue的構(gòu)造函數(shù)里面,創(chuàng)建了一個以100毫秒為間隔的定時器,這個定時器成為以上所有的Process所需要的基
礎(chǔ)。我們看它的UploadTimer就可以看出這一點。這里面充斥了各個類的Process方法的執(zhí)行,其中包括以前我們提到的一些類,但是沒有提到它
們的Process方法,因為其過于簡單,基本上就只是更新了一下要保存的信息。
18. eMule源代碼學(xué)習(xí)心得(18):emule中代碼量最大的類CUpDownClient
CUpDownClient
類的作用是從邏輯上表示一個其它的客戶端的各種信息,它是emule中代碼量最大的類。我們注意到,定義它的頭文件是UpDownClient.h,但是
卻沒有對應(yīng)的CUpDownClient.cpp,而它的實現(xiàn),都分散到BaseClient.cpp,DownloadClient.cpp,
PeerCacheClient.cpp,UploadClient.cpp和URLClient.cpp中。
BaseClient.cpp
中實現(xiàn)的是該類的一些基本的功能,包括基本的各種狀態(tài)信息的獲取和設(shè)置,以及按照要求處理和發(fā)送各種請求。在這里,邏輯實現(xiàn)和網(wǎng)絡(luò)進行了區(qū)分,
CUpDownClient類本身不從網(wǎng)絡(luò)接受或者發(fā)送消息,它只是提供各種請求的處理接口,以及在發(fā)送請求時,構(gòu)造好相應(yīng)的Packet,并交給自己對
應(yīng)的網(wǎng)絡(luò)套接字發(fā)出去。
DownloadClient.cpp中實現(xiàn)的是和下載相關(guān)的功能,它包括了各種下載請求的發(fā)送以及相應(yīng)的數(shù)據(jù)的接
收。另外還有一個A4AF的機制,它是emule中的一個機制,因為一個客戶端在同一個時間內(nèi)只能向另外一個客戶端請求同一個文件。這樣,對于很多個下載
任務(wù)(CPartFile),有可能出現(xiàn)它們的源(即有該文件的客戶端)有部分重疊的現(xiàn)象,而這時,如果其它下載任務(wù)正在從這個源下載,那么當(dāng)前的下載任
務(wù)就不能從這個源下載了。但是emule允許用戶對其手動進行控制,如對下載任務(wù)的優(yōu)先級進行區(qū)分,這樣他就可以將一個源從另外一個下載任務(wù)那里切換過
來。A4AF其實就是ask for another file的簡稱。
UploadClient.cpp中實現(xiàn)的是上傳相關(guān)功能,即接受進來的下載請求,并且生成相應(yīng)的文件塊發(fā)送出去。
PeerCacheClient.cpp
實現(xiàn)的是和PeerCache相關(guān)的功能,PeerCache是一個由Joltid公司開發(fā)的技術(shù),它可以允許你從ISP提供的一些快照服務(wù)器上快速得上
傳或者下載一些文件(或者是一部分),這個技術(shù)的好處是可以減少骨干網(wǎng)絡(luò)的帶寬消耗,將部分本來需要在骨干網(wǎng)上走的流量轉(zhuǎn)移到ISP的內(nèi)部。當(dāng)然這個功能
需要ISP的配合。如果發(fā)現(xiàn)ISP提供了這項服務(wù)的話,emule會利用它來減少骨干網(wǎng)的帶寬消耗。
URLClient.cpp實現(xiàn)的功能是利用http協(xié)議對原有的emule協(xié)議進行包裝,以便使它能夠盡可能地穿越更多的網(wǎng)絡(luò)的防火墻。
19. eMule源代碼學(xué)習(xí)心得(19):emule常規(guī)部分小結(jié)
emule
中還有其它的很多類,它們使得emule的功能更加的強大和完善。有很多類在前面沒有提到,但是不代表它沒有作用。而且即時是前面提到的類也只是大體的介
紹,它們之間互相配合的一些細節(jié)沒有體現(xiàn)。但是這些細節(jié)應(yīng)該已經(jīng)可以通過對它們的大體的功能的了解而更加容易被把握。至于GUI的設(shè)計,它也最終是要對應(yīng)
到某個功能實現(xiàn)類的數(shù)據(jù)的。
對于emule中的通信協(xié)議只是大體得描述了一下它的數(shù)據(jù)包的格式,但是并沒有詳細得描述它的每一個
Opcode對應(yīng)的包的意義,因為我認為這是沒有必要的,在知道通信協(xié)議的格式以及處理它們的代碼所在的位置后,可以很簡單的通過追蹤某條消息的前因后果
把整個通信協(xié)議都分析出來。
這里再稍微提一下在emule中使用到的其它類及其功能。我們可以看到,如果單純只是為了能夠搜到以及下載到文
件的話,有不少類是可以精簡的,但是,正是由于它們的存在,使得emule的功能更加的完善。CIPFilter,IP地址過濾器,通過識別各種類型的
IP地址過濾信息,它能夠把不希望連接的網(wǎng)絡(luò)地址過濾掉,emule中所有需要連接網(wǎng)絡(luò)的地方使用的都是統(tǒng)一的過濾數(shù)據(jù)。CWebServer能夠在本地
打開一個Web服務(wù)器,然后你可以通過瀏覽器來控制你的emule。CScheduler能夠?qū)崿F(xiàn)下載任務(wù)的定時下載。CPeerCacheFinder
為前面提到的PeerCache技術(shù)的主控制類。另外,emule還內(nèi)置了一個IRC客戶端,一個主要成員函數(shù)都為靜態(tài)的
CPartFileConvert類,能夠?qū)ζ渌姹镜捏H的下載文件進行轉(zhuǎn)換。它甚至還提供了一個自動處理zip和rar的類
CArchiveRecovery。
Kademlia網(wǎng)絡(luò)是emule中相當(dāng)重要的一部分,因此特意把這一部分單獨拿出來,把它放在這個小結(jié)之后進行描述。
20. eMule源代碼學(xué)習(xí)心得(20):emule中的Kademlia代碼總體描述
首先要說一下抱歉,這兩個星期的事情比較多,所以現(xiàn)在才來寫。不過總算是把這些東西寫完了。而且最后用的代碼是0.47c。
當(dāng)emule
中開始使用Kademlia網(wǎng)絡(luò)后,便不再會有中心服務(wù)器失效這樣的問題了,因為在這個網(wǎng)絡(luò)中,沒有中心服務(wù)器,或者說,所有的用戶都是服務(wù)器,所有的用
戶也是客戶端,從而完完全全得實現(xiàn)了P2P。接下來講針對emule中的Kademlia網(wǎng)絡(luò)進行分析,會有一節(jié)進行原理方面的分析。另外的幾節(jié)將會根據(jù)
emule中實現(xiàn)Kademlia所使用的不同的類分別進行講述。其中:
CKademlia是整個Kademlia網(wǎng)絡(luò)的主控類,可以直接開始或者停止Kademlia網(wǎng),并且含有Process方法來處理日常事務(wù)。
CPrefs負責(zé)處理自身的Kademlia相關(guān)信息,如自身的ID等。
CRoutingZone,CRoutingBin和CContact三個類組成了每個節(jié)點所了解的聯(lián)系信息以及由這些聯(lián)系信息所組成的數(shù)據(jù)結(jié)構(gòu)。
CKademliaUDPListener負責(zé)處理網(wǎng)絡(luò)信息。
CIndexed負責(zé)處理本地存儲的索引信息。
CSearch,CSearchManager負責(zé)處理和搜索有關(guān)的操作,其中前者表示的是一個單一的搜索任務(wù),后者負責(zé)對所有搜索任務(wù)進行處理。
CUInt128負責(zé)處理一個128位的長整數(shù),并且內(nèi)置其各種運算。前面已經(jīng)提到過
21. eMule源代碼學(xué)習(xí)心得(21):emule中的Kademlia的基本原理
Kademlia
是一種結(jié)構(gòu)化的覆蓋網(wǎng)絡(luò)(Structured Overlay
Network),所謂的覆蓋網(wǎng)絡(luò),就是一種在物理的Internet上面再次構(gòu)建的虛擬網(wǎng)絡(luò),所有參與的節(jié)點都知道一部分其它節(jié)點的IP地址,這些節(jié)點
稱為它的鄰居,如果需要查找什么東西,它先在本地尋找,如果找不到,就把這個查詢轉(zhuǎn)發(fā)到它的鄰居處,希望能夠有可能查找到相應(yīng)的結(jié)果。覆蓋網(wǎng)絡(luò)里面分成了
結(jié)構(gòu)化和非結(jié)構(gòu)化的兩種情況,它們的區(qū)別在于每個節(jié)點知道哪些其它節(jié)點的信息是否有特定的規(guī)律。在非結(jié)構(gòu)化的覆蓋網(wǎng)中,每個節(jié)點的鄰居狀況沒有特定的規(guī)
律。因此在非結(jié)構(gòu)化網(wǎng)絡(luò)中,如果要進行查詢,會采取一種叫做泛洪(flooding)的方法,每個節(jié)點如果在本地沒有查找到想要的結(jié)果,會把查找請求轉(zhuǎn)發(fā)
到它的鄰居中,然后再通過鄰居的鄰居這種方式來進行一步步的查找。但是這種方法如果處理不好,會造成整個網(wǎng)絡(luò)的消息負載過大。已經(jīng)有不少文章對于優(yōu)化非結(jié)
構(gòu)化覆蓋網(wǎng)絡(luò)中的查詢進行了很深入的探討。
對于結(jié)構(gòu)化的覆蓋網(wǎng)絡(luò),它的特點是每個節(jié)點它會選擇和哪些節(jié)點做鄰居是有一定的規(guī)律的,從而在進
行搜索的時候,節(jié)點把搜索請求進行轉(zhuǎn)發(fā)的時候它能夠通過一定的規(guī)律進行選擇把請求轉(zhuǎn)發(fā)到哪些鄰居節(jié)點上。這樣同時也能減少搜索代價。結(jié)構(gòu)化的覆蓋網(wǎng)絡(luò)通常
要求每一個節(jié)點隨機生成一個ID,用以判斷各個節(jié)點之間的關(guān)系。這個ID和它所在的物理網(wǎng)絡(luò)必須是沒有關(guān)系的。
對于Kademlia網(wǎng)絡(luò)來
說,這個ID是一個128位的數(shù)值,所有的節(jié)點都用這個ID來衡量自己與其它節(jié)點的邏輯距離。而邏輯距離的計算方法就是將兩個節(jié)點進行異或(XOR)操
作。在Kademlia網(wǎng)絡(luò)的形成過程中,每個節(jié)點選擇鄰居的原則是離自己邏輯距離越近的節(jié)點越有可能被加入到自己的鄰居節(jié)點列表中,具體來說就是在每次
新得到一個節(jié)點的信息的時候,是否把它加入到自己的鄰居節(jié)點列表是根據(jù)距離的遠近來處理的。后面分析具體程序的代碼時會有說明。
結(jié)構(gòu)化的網(wǎng)
絡(luò)的好處就是如果我們要尋找一個距離某個ID邏輯距離足夠近的節(jié)點,我們可以保證在O(logn)級別的跳數(shù)找到。只要先尋找自己已知的離目標(biāo)ID邏輯距
離足夠斷的節(jié)點,然后再問它知不知道更近的,然后就這樣下去。因此在搜索的時候也是這樣,當(dāng)需要發(fā)布資源的時候,把文件進行hash,這樣就能夠計算出一
個128位的ID,或者把關(guān)鍵字進行hash。然后尋找到離這個結(jié)果邏輯距離最近的節(jié)點,把文件或者關(guān)鍵字的信息發(fā)送給它,讓它存起來。當(dāng)有人要搜索同樣
的東西的時候,由于它用的是同一個hash算法,因此能夠計算出對應(yīng)的ID,并且去搜索那些和這個ID邏輯距離相近的節(jié)點,因為它知道,如果網(wǎng)絡(luò)中真有這
些資源的話,這些節(jié)點是最有可能知道這些信息的。由此我們可以看出,結(jié)構(gòu)化的網(wǎng)絡(luò)的資源查找效率是很高的,但是它和非結(jié)構(gòu)化的覆蓋網(wǎng)絡(luò)比起來,缺點是不能
進行復(fù)雜查詢,即只能通過簡單的關(guān)鍵字或者文件的hash值進行查找。非結(jié)構(gòu)化的網(wǎng)絡(luò)的查找本身就是隨意轉(zhuǎn)發(fā)的,每個收到的查詢請求的節(jié)點都對本地的資源
掌握的很清楚,因此自然可以支持復(fù)雜查詢,但是顯然非結(jié)構(gòu)化的網(wǎng)絡(luò)支持的復(fù)雜查詢不太可能動員所有的節(jié)點都來做這一動作。目前還沒有方法能夠把兩種覆蓋網(wǎng)
絡(luò)的優(yōu)點結(jié)合起來,我也非常想知道這樣的一種方法。
22. eMule源代碼學(xué)習(xí)心得(22):emule中的Kademlia的基礎(chǔ)設(shè)施類
Kademlia
的主控類是CKademlia,它負責(zé)啟動和關(guān)閉整個Kademlia網(wǎng)的相關(guān)代碼。在它的Process函數(shù)中,會處理和Kademlia網(wǎng)相關(guān)的事
務(wù),例如隔一段時間檢查某個區(qū)間的節(jié)點數(shù)是否過少,如果是則尋找一些新的節(jié)點。另外經(jīng)常對自己的鄰居進行檢查等,這些都是屬于需要進行日常安排的工作。所
有搜索任務(wù)的日常處理也需要它來調(diào)度。它還作為Kademlia網(wǎng)的代表,向emule其它部分的代碼返回Kademlia網(wǎng)的一些統(tǒng)計信息。
另一個基礎(chǔ)設(shè)施類是CPrefs,它和emule普通代碼中的CPreferences作用類似,但是CPrefs只保留和Kademlia網(wǎng)相關(guān)的,需要長期保存的本地信息。具體到這個版本來說,主要就是本地的ID。
還有一個很重要的基礎(chǔ)設(shè)施就是CUInt128,實現(xiàn)對128位的ID的各種處理,前面的部分已經(jīng)提到。
23. eMule源代碼學(xué)習(xí)心得(23):emule中的Kademlia的聯(lián)系人列表管理
CRoutingZone,CRoutingBin和CContact三個類組成了聯(lián)系人列表數(shù)據(jù)結(jié)構(gòu)。它要達到我們搜索的要求,即搜索到目標(biāo)的時間要能夠接受,而且所占用的空間也要能夠接受。
首
先CContact類包含的是一個聯(lián)系人的信息,主要包括對方的IP地址,ID,TCP端口,UDP端口,kad版本號和其健康程度
(m_byType)。其中健康程度有0-4五個等級。剛剛加入的聯(lián)系人,也就是健康狀況未知的,這個數(shù)值設(shè)置為3。系統(tǒng)會經(jīng)常通過與各個聯(lián)系人進行聯(lián)系
的方式對其進行健康狀況檢查,經(jīng)常能夠聯(lián)系上的聯(lián)系人,這個數(shù)值會慢慢減少到0。而很就沒有聯(lián)系的,這個數(shù)值會慢慢增加,如果增加到4后再過一段時間未能
成功聯(lián)系上的,則將會被從聯(lián)系人列表中刪除。
CRoutingBin類包含一個CContact的列表。這里要注意的是要訪問聯(lián)系人的信息
必須通過某個CRoutingBin,CRoutingZone內(nèi)部是不直接包含聯(lián)系人信息的。可以把新的聯(lián)系人信息往一個特定的CRoutingBin
中加,當(dāng)然也可以進行聯(lián)系人查找。它也提供方法能夠?qū)ふ页鲭x某個ID距離最近的聯(lián)系人,并給出這樣的一個列表。這是相當(dāng)重要的。最后,一個
CRoutingBin類中能夠包含的CContact的數(shù)量也是有限制的。
CRoutingZone類處于聯(lián)系人數(shù)據(jù)結(jié)構(gòu)的最上層,直接
為Kademlia網(wǎng)提供操作接口。該類的結(jié)構(gòu)為一個二叉樹,內(nèi)含兩個CRoutingZone指向它的左子樹和右子樹,另外也包含一個
CRoutingBin類型的指針。但是只有在當(dāng)前的CRoutingZone類為整個二叉樹的葉節(jié)點時,這個指向CRoutingBin類型的指針才有
意義。這個二叉樹的特點是,每個節(jié)點以下的所有聯(lián)系人的ID都包含一個共同前綴,節(jié)點的層數(shù)越深,這個共同前綴越長。例如,根節(jié)點的左子樹的所有的節(jié)點的
ID一定有一個前綴"0",而右子樹的所有節(jié)點一定有前綴"1"。同樣,根節(jié)點的左子樹的右子樹下的所有節(jié)點的ID一定有前綴"01",等等,依此類推。
我們設(shè)想一下節(jié)點不斷得往這個二叉樹添加的過程。剛開始只有一個根節(jié)點,它也就是葉節(jié)點,這時它內(nèi)部的CRoutingBin是有意義的,當(dāng)聯(lián)系人信息不
斷得被添加進去以后,這個CRoutingBin的容量滿了,這時要進行的就是一個分裂的操作。這時,會添加兩個左子節(jié)點和右子節(jié)點,然后把自身的
CRoutingBin中的聯(lián)系人信息按照它們的前綴特點分別復(fù)制往左節(jié)點和右節(jié)點,最后把自身的CRoutingBin廢除掉,這樣這個分裂過程就完
了。當(dāng)分裂完成后,就會再次試圖添加該聯(lián)系人信息,此時會試圖按照它的ID,把它添加到對應(yīng)的子樹中。但是并不是所有的這種情況節(jié)點都會發(fā)生分裂,因為如
果允許任意分裂的話,本地所需存儲的節(jié)點信息數(shù)量就會急劇上升。這里,自身ID的作用就體現(xiàn)了。只有當(dāng)自身ID和當(dāng)前準(zhǔn)備分裂的節(jié)點有共同前綴時,這個節(jié)
點才會分裂,而如果判斷到一個節(jié)點不能分裂,而它的CRoutingBin又滿掉了,那么就會拒絕添加聯(lián)系人信息。
我們可以看出,在以上政
策的進行下,離自身ID邏輯距離越近(也就是共同前綴越長)的聯(lián)系人信息越有可能被加入,因為它所對應(yīng)的節(jié)點越有可能因為分裂而獲得更多的子節(jié)點,也就對
應(yīng)了更多的容量。這樣,在Kademlia網(wǎng)中,每一個參與者知道的其它參與者信息中,離自己邏輯距離越近的參與者比例越高。由于在搜索的時候也只需要不
斷得尋找更近的ID,而且每一步都一定會有進展,所以尋找到目標(biāo)ID所需要的時間上的代價是O(logn),從這個二叉樹的結(jié)構(gòu)來看,我們也可以看到,由
于只有部分節(jié)點會分裂,所以實質(zhì)上存儲所需要的空間代價也是O(logn)。
實際上CRoutingZone在實現(xiàn)時和理論上的Kademlia有一些區(qū)別,如從根節(jié)點開始,有一個最低分裂層數(shù),也就是說,如果層數(shù)過低的話,是永遠允許分裂的,這樣它知道的其它地區(qū)的聯(lián)系人信息就能夠稍微多一些。
24. eMule源代碼學(xué)習(xí)心得(24):emule中的Kademlia網(wǎng)絡(luò)消息處理
CKademliaUDPListener
負責(zé)處理所有和Kademlia網(wǎng)相關(guān)的消息。前面已經(jīng)對emule的通信協(xié)議的基本情況做了一個大概的描述,我們就可以知道,
CKademliaUDPListener處理的消息一定是只和Kademlia網(wǎng)相關(guān)的,分揀工作已經(jīng)在emule的普通UDP客戶端處理代碼那里處理
好了。具體的消息格式前面也有一些介紹,下面會就一些具體的消息分類做說明。
首先是健康檢查方面的消息,這樣的消息就是一般的ping-
pong機制。對應(yīng)的消息有KADEMLIA_HELLO_REQ和KADEMLIA_HELLO_RES。當(dāng)對本地聯(lián)系人信息列表進行檢查時,會對它們
發(fā)出KADEMLIA_HELLO_REQ消息,然后處理收到的KADEMLIA_HELLO_RES消息。
最常用的消息是節(jié)點搜索消息,
在Kademlia網(wǎng)絡(luò)中,進行節(jié)點搜索是日常應(yīng)用所需要傳輸?shù)闹饕?,它的實現(xiàn)方式是迭代式的搜索。這種方式就是說當(dāng)開始搜索某個ID時,在本地聯(lián)系
人信息列表中查找到距離最近的聯(lián)系人,然后向它們發(fā)出搜索請求,這樣通常都能夠得到一些距離更近的聯(lián)系人信息,然后再向它們發(fā)送搜索請求,通過不斷得進行
這樣的搜索查詢,就能夠得到距離目標(biāo)ID最近的那些聯(lián)系人信息。這里對應(yīng)的消息代碼是KADEMLIA_REQ和KADEMLIA_RES。
接
下來就是對內(nèi)容進行發(fā)布或者搜索。這一點結(jié)合后面的CIndexed類的分析可以知道得更加清楚。emule中存儲在Kademlia網(wǎng)中的信息主要有三
類:文件源,關(guān)鍵字信息和文件的評論。文件源對應(yīng)的是每一個具體的文件,每個文件都用它的內(nèi)容的hash值作為該文件的唯一標(biāo)示,一條文件源信息就是一條
關(guān)于某人擁有某個特定的文件的這樣一個事實。一條關(guān)鍵字信息則是該關(guān)鍵字對應(yīng)了某個文件這樣一個事實。很顯然,一個關(guān)鍵字可能會對應(yīng)多個文件,而一個特定
的文件的文件源也很有可能不止一個。但是它們的索引都以固定的hash算法作為依據(jù),這樣使得搜索和發(fā)布都變得很簡單。
我們來看發(fā)布過程。
每個emule客戶端把自己的共享文件的底細已經(jīng)摸清楚了,在傳統(tǒng)的有中心索引服務(wù)器的場景里,它把自己的所有文件的信息都上傳到中心索引服務(wù)器里。但是
在Kademlia網(wǎng)里,它就需要分散傳播了,它首先做的事情是把文件名進行切詞,即從文件名中分解出一個一個的關(guān)鍵詞出來,它切詞的方法非常簡單,就是
在文件名中尋找那些有分割符含義的字符,如下劃線等,然后把文件名切開。計算出這些關(guān)鍵字的hash值后,它把這些關(guān)鍵字信息發(fā)布到對應(yīng)的聯(lián)系人那里。并
且把文件信息也發(fā)布到和文件內(nèi)容hash值接近的聯(lián)系人那里。對應(yīng)的消息是KADEMLIA_PUBLISH_REQ和
KADEMLIA_PUBLISH_RES。另外emule允許用戶對某個文件發(fā)表評論,評論的信息單獨保存,但是原理也是一樣的。
當(dāng)用戶
使用Kademlia網(wǎng)絡(luò)來進行搜索并且下載文件的時候,首先是對一個關(guān)鍵詞進行搜索,由于使用的是同樣的hash算法,這樣它只要找到ID值和計算出來
的hash值結(jié)果相近的聯(lián)系人信息后,它就可以直接向它們發(fā)送搜索特定關(guān)鍵詞的請求了。如果得到了返回信息,那么搜索者就知道了這個關(guān)鍵詞對應(yīng)了多少文
件,然后把這些文件的信息都列出來。當(dāng)用戶決定下載某個文件的時候,針對這一特定文件的搜索過程就開始了,這一次如果搜索成功,那么返回的就是這個文件的
文件源信息。這樣emule接下來就只需要按照這些信息去連接相應(yīng)的地址,并且使用傳統(tǒng)的emule協(xié)議去和它們協(xié)商下載文件了。這里對應(yīng)的消息是
KADEMLIA_SEARCH_REQ和KADEMLIA_SEARCH_RES。
實際的實現(xiàn)中有Kademlia2這種協(xié)議,它的原理
是一樣的,只有協(xié)議代碼和具體的消息格式不一樣,例如KADEMLIA_REQ和KADEMLIA_RES對應(yīng)了KADEMLIA2_HELLO_REQ
和KADEMLIA2_HELLO_RES,但是后者在具體的消息中包含了比前者豐富一些的信息。在實現(xiàn)的時候0.47c更加傾向于使用
Kademlia2,而0.47a更加傾向于使用Kademlia。當(dāng)然,它們兩種協(xié)議都能夠處理。另外,0.47c增加了一個對于已發(fā)出的請求的追蹤的
特性,就是一個包含TrackPackets_Struct類型的列表,這里面詳細紀(jì)錄了什么時間曾經(jīng)對哪個IP發(fā)出過那種opcode對應(yīng)的請求。為什
么要這樣呢?這是為了防止針對DHT的一種路由污染攻擊,因為在搜索聯(lián)系人的時候,如果搜索到了一些聯(lián)系人信息,也會試圖把它先加入到本地的聯(lián)系人信息列
表中。這樣如果有人想惡意攻擊的話,它只要不斷得往它想攻擊的emule客戶端發(fā)送KADEMLIA_RES,并且在消息的內(nèi)容中包含大量的虛假聯(lián)系人信
息,就可以使對方的聯(lián)系人信息列表中充滿垃圾。這樣,由于缺少正確有效的聯(lián)系人信息,它的Kademlia網(wǎng)功能基本上就廢了。而在0.47c里面增加的
這個特性,就會對那種還沒有發(fā)出請求就收到回應(yīng)的情況直接無視,從而避免被愚弄。
25. eMule源代碼學(xué)習(xí)心得(25):emule中的Kademlia的分布式索引管理
Kademlia
網(wǎng)絡(luò)的最大的好處是把原來需要存儲到中心索引服務(wù)器中的信息分散存儲到各個客戶端當(dāng)中,如果要說得更加準(zhǔn)確一點,那我們就可以說它把這些信息分散得存儲到
各個emule客戶端的CIndexed類當(dāng)中。我們可以具體開始看CIndexed的設(shè)計,看它是如何完成這一工作的。在這之前我們要稍微詳細得說一下
emule發(fā)布到Kademlia網(wǎng)絡(luò)中的信息的各種類型。
一個文件源信息是一個文件內(nèi)容的hash值和擁有這個文件的客戶端的IP地址,
各種端口號以及其它信息之間的對應(yīng)關(guān)系。而一個關(guān)鍵詞信息則是該關(guān)鍵詞和它對應(yīng)的文件之間的關(guān)系。在關(guān)鍵詞信息中,它對應(yīng)的文件信息要更加詳細,通常包括
這個文件的文件名,文件大小,文件內(nèi)容的hash值,如果是MP3或者其它媒體文件,還會包含包括作者,生產(chǎn)時間,文件長度(這個長度是用時間來衡量的媒
體文件的播放長度),流派等等tag信息。其中文件內(nèi)容的hash值用來區(qū)分該關(guān)鍵詞對應(yīng)的不同文件。
CIndexed中利用了一系列的
Map來存儲這些對應(yīng)信息,CMap是MFC中實現(xiàn)標(biāo)準(zhǔn)STL中的map的模板類,CIndexed中包含了四個這樣的類,分別用來存儲文件源信息,關(guān)鍵
詞信息,文件評論信息以及負載信息。其中文件評論信息是不長久保存的,而其它的信息都會在退出的時候?qū)懙轿募?,下次重新啟動emule時再重新調(diào)入。另
外負載信息不是等其它聯(lián)系人來發(fā)布的,而是根據(jù)文件源信息和關(guān)鍵詞信息的發(fā)布情況自行進行動態(tài)調(diào)整的。每一次收到發(fā)布信息時,對應(yīng)的ID的負載會增大,這
一事實會在回應(yīng)消息(KADEMLIA_PUBLISH_RES)中體現(xiàn)。
CIndexed中的信息會經(jīng)常進行檢查,每隔三十分鐘它會把自
己存儲的所有信息中太老的信息清除掉。其中文件源信息的保存時間為五小時,關(guān)鍵詞信息為二十四小時,文件評論的信息保存時間也為二十四小時。因此文件的發(fā)
布和關(guān)鍵詞也要周期性得反復(fù)進行。其實這對于整個Kademlia網(wǎng)絡(luò)的穩(wěn)定性也是有好處的,因為每一次聯(lián)系都會試圖把對方添加到自己的聯(lián)系人列表中,或
者在聯(lián)系人列表中標(biāo)注上一次見到對方的時間。
CIndexed為其它部分的代碼提供了它們所需要的增加信息和搜索信息的接口,這樣在從網(wǎng)絡(luò)中獲取到相關(guān)的搜索或者發(fā)布請求,并且CKademliaUDPListener完成消息的解釋后,就可以交給CIndexed來進行處理了。
26. eMule源代碼學(xué)習(xí)心得(26):emule中的Kademlia搜索任務(wù)管理
CSearch
和CSearchManager是完成具體搜索任務(wù)的。CSearch對應(yīng)的是一個具體的搜索任務(wù),它包括了一個搜索任務(wù)從發(fā)起到結(jié)束的全部過程,要注意
的是搜索任務(wù)并不只是指搜索文件源或者關(guān)鍵詞的任務(wù),一次發(fā)布任務(wù)它也需要創(chuàng)建一個CSearch對象,并且讓它開始執(zhí)行。CSearchManager
則掌握所有的搜索任務(wù),它包含了一個包含所有CSearch指針對象的CMap,使用CMap的原因是因為所有的CSearch都一定對應(yīng)一個ID,那個
ID就是該CSearch所對應(yīng)的目標(biāo),不管是要查找節(jié)點,還是要搜索或者發(fā)布信息,一定都要找到和目標(biāo)ID相近的聯(lián)系人。因此
CSearchManager可以使用CMap來表示所有的搜索任務(wù)。
我們注意到CSearch在創(chuàng)建的時候就把自己加入到
CSearchManager當(dāng)中。另外CSearch在創(chuàng)建的時候需要說明它的類型,例如是只是為了搜索節(jié)點還是要搜索關(guān)鍵詞信息或者文件源信息,當(dāng)然
也有可能是發(fā)布文件源信息或者關(guān)鍵詞信息。我們介紹一下CSearch的幾個方法的作用就可以大概了解CSearch的工作過程。Go是它的啟動過程,它
會開始第一次從本地的聯(lián)系人列表中尋找候選的聯(lián)系人,然后開始發(fā)動搜索。SendFindValue的功能就是向某個聯(lián)系人發(fā)送一個搜索某ID的聯(lián)系人信
息這樣一個請求。JumpStart則是在搜索進行到一定地步的時候,如得到了一些中間結(jié)果,開始進行下一步的行動,下一步的行動仍然可能是
SendFindValue,也有可能認為搜索到的聯(lián)系人離目標(biāo)已經(jīng)足夠近了,于是就可以開始實質(zhì)性的請求。StorePacket就是這樣一個實質(zhì)性的
請求,例如在一個以發(fā)布文件源為任務(wù)的CSearch中,StorePacket會向目標(biāo)聯(lián)系人發(fā)送
KADEMLIA2_PUBLISH_SOURCE_REQ(如果不支持Kademlia2,那么是KADEMLIA_PUBLISH_REQ)。最后,
CSearch能夠處理各種搜索結(jié)果,然后向調(diào)用它的代碼返回處理好的結(jié)果。
CSearchManager直接和Kademlia網(wǎng)的其它
部分代碼接觸,例如,如果CKademliaUDPListener搜索到了一些結(jié)果,它會把這些結(jié)果交給CSearchManager,然后
CSearchManager再去尋找這個結(jié)果是屬于那個搜索任務(wù)的,并且進行轉(zhuǎn)交。另外CSearchManager對外提供創(chuàng)建各種新的搜索任務(wù)的接
口,作用類似于設(shè)計模式中的Factory,其它部分的代碼只需要說明需要開始一個什么樣的搜索任務(wù)即可,CSearchManager來完成相應(yīng)的創(chuàng)建
CSearch的任務(wù)。
27. eMule源代碼學(xué)習(xí)心得(27):后記
終于把自己想干的另一件事情干完了。eMule的代碼寫得
很好,里面有很多都是值得我們學(xué)習(xí)的地方。由于時間關(guān)系,我也只看了那些和實現(xiàn)最基本的功能相關(guān)的代碼,而實際上eMule里面還有很多代碼實現(xiàn)了很多很
有意思的功能。eMule的GUI設(shè)計的代碼也是很不錯的,但是我也沒有來得及看。最后,歡迎大家來和我討論關(guān)于P2P技術(shù)方面的問題。