【對轉載進行了部分解釋】CRoutingZone的實現機制 以及 process()中一些函數的意義

Keywords:
  • CRoutingZone
  • OnBigTimer
  • OnSmallTimer
  • CUinit128
  • eMule
  • Kad
  • 二叉樹
  • m_uZoneIndex
  • m_uLevel

From:
http://blog.sina.com.cn/s/blog_4a8804c901008pnr.html

【對原文進一步進行了註釋,個人思考,未必正確,還望指教】

  1. CUInt128裏面用4個32位整數(m_uData)保存一個128bits的值。它的MSB是m_uData[0]的最高位,LSB是m_uData[3]的最低位。
     
  2. CRoutingZone代表一個二叉樹的節點,他可以是中間節點,也可以是葉子節點。通過其下的CRoutingBin實例是否爲空來判斷,如果不爲空,則爲一個葉子節點,反之則是中間節點。CRoutingBin實例中存放的是同這個CRoutingZone具有相同前綴的聯繫人。

  3. CRoutingZone裏面有個member叫m_uLevel,它代表這個CRoutingZone包括了整個ID空間的多少子空間。0表示整個空間,1表示一半。但是這樣不利於理解的使用方法。它還可以看成二叉樹節點的深度。最重要的是他代表了這個CRoutingZone的前綴範圍是m_uLevel。根節點爲0,說明根CRoutingZone沒有前綴範圍,所以任何聯繫人都可以加入根節點,m_uLevel爲3所明這個CRoutingZone的前綴是3BIT,只有ID的前三個BIT和CRoutingZone的前綴相同的聯繫人才會放到他的下面或他的子樹下面。

    【Ex:但是真正在eMule的Kad中這個前綴是如何設計的呢?其實無所謂,只是在eMule中的peer更新機制中有參數與這些前綴的長度有關】

  4. CRoutingZone下有m_pSubZones[2]這樣一個數組指針,用來存放二叉子樹。在當前的子樹深度Level上,同本地ID最近的聯繫人放在m_pSubZones[0]及其子樹下面,同本地較遠的聯繫人放在m_pSubZones[1]及其子樹下面。也就是比較聯繫人的ID和本地ID的距離,然後找到對應子樹深度Level上的那一個BIT值,爲0表示和本地ID更近,爲1則遠。這樣的表示方法雖然和Kademlia描述不太一樣,但是並不衝突。如果本地ID在這一級上的BIT爲1,則所有在這一級爲0的聯繫人放到m_pSubZones[1]中,爲1的聯繫人放到m_pSubZones[0]中。而Kademlia中描述是直接按照聯繫人ID在這一位的BIT值(而不是距離)來存放到不同的子樹中去的(即先放0,再放1,不管本地ID在當前級的bit是多少)eMule的方法僅僅是讓兩棵子樹調換了一下位置,但結構和父子關係是不變的

    【要知道在二叉樹中,左右節點是嚴格區分的,和普通樹中的概念有區分。這裏左節點放更近的,右節點次之。】

  5. CRoutingZone裏有個m_uZoneIndex,不太好理解,註釋也看不太懂。仔細研究後發現,它和m_uLevel一起定義了這個CRoutingZone在前綴上與本地ID的距離。比如CRoutingZone的前綴是4,本地ID前4BIT爲“1101”,一個聯繫人的ID前4BIT爲“1100”,它的CRoutingZone位於二叉樹的位置從根節點開始其路徑是0->0->0->1(0表示子樹0,1爲子樹1),m_uZoneIndex 則爲“0001”,也就是CRoutingZone位於二叉樹的位置,應爲聯繫人是按照距離來對應到二叉樹的某個節點上的。(分析這個費心了,我也看了好久才理解)

    根據異或運算的特點,知道了距離和本地ID前綴,我們就可以通過再次異或運算求出聯繫人ID前綴。
    【這是當然的,如果都是0,表示和本地ID對應位置一致,是1就表示反之了】
    【異或就是不進位的加法】


    m_uZoneIndex還有另一個作用,那就是記錄了CRoutingZone在某一深度的序號。在同一深度,CRoutingZone結點的序號是遞增的,離本地ID越遠,序號也就是m_uZoneIndex越大。他可以用來控制每個結點的分裂,來避免結點的指數性增長。
    【這句很好理解,因爲與本地節點一致,就放在左子節字,並且m_uZoneIndex當前位就爲0,以上文的例子來說,最近的當然是:0000,其次就是0001,表示左邊子樹的右葉子,然後就是0010,就是右邊子樹的左葉子,0011:右邊子樹的右葉子。當然這個左右劃分是爲了理解,在實際實現中,這裏是0,1兩個數組序號】

  6. 每次建立一個新的CRoutingZone,他都會在CKademlia裏的m_mapEvents里加入一個記錄。CKademlia在Process()函數裏會對每個記錄的CRoutingZone進行操作。一旦這個CRoutingZone分裂出兩個子CRoutingZone時,它就會將自己從m_mapEvents裏面拿掉。所以只有葉子結點纔會被執行到。具體的操作是在CKademlia::Process()里根據時間來執行的。

  7. CKademlia::Process()是個普通函數,他在CUploadQueue::UploadTimer()中被調用。而UploadTimer是一個定時器的CallBack函數,每隔100ms調用一次。也就是說Process()函數是每100ms運行一次。

    • CRoutingZone裏面有一個m_tNextBigTimer,表示的是下一次運行OnBigTimer的時間(以秒來計算)。

    • CKademlia也有一個m_tBigTimer的時間,表示下次CKademlia::Process() 檢查每個CRoutingZone的m_NextBigTimer的時間。

    • m_tBigTimer每次更新爲當前時間加上10秒鐘

    • CRoutingZone::m_tNextBigTimer的更新每次爲當前時間加上1小時

    • 總體來說就是CKademlia每10秒鐘檢查每個登記了的CRoutingZone的NextBigTimer,如果到了就執行OnBigTimer()。
      而CRoutingZone設置NextBigTimer來控制每1個小時運行一次OnBigTimer()。

      【到底怎麼回事?其實因爲CKad..中維護了很多CRoutingZone對象,它要在一個較短時間內,這裏爲10s來檢查這些CRZ對象,那個需要OnBigTimer了,但是對於CRZ對象本身,則是設置了1個小時,直到這個時間到了,纔可能觸發。當然這就需要Process不斷運轉了,前文提到100ms就循環,夠可以的吧。】

      【再看小Timer,就是OnSmallTimer,用於檢測聯繫人在線否,發送hello req消息,1分鐘就執行一次。要這麼快麼?】

      【我之前誤解了,其實process並不處理報文,只有processpacket處理報文,所以它的tiimer設置主要是爲了更新狀態等】

  8. OnBigTimer()裏面只是查找一個假想的Peer,目的是獲得更多的在這個CRoutingZone裏面的實際Peer信息。必須是葉子節點才能執行。並且還好滿足下列條件:m_uZoneIndex < KK || m_uLevel < KBASE || m_pBin->GetRemaining() >= (K*.8)。【KK=5,KBASE=4, K=10】

    【這個方法很巧妙,其實在裏面構建的隨機查找的ID,前綴是根據m_uZoneIndex 生成的,後續位數通過128-m_uLevel得到,然後通過通過Uint128的構造函數隨機補充爲一個128位的ID】

    【查找過程:先找到自己聯繫人中的相近的(CSearch的Go函數中),然後向他們發送執行 SendFindValue(),發送KADEMLIA2_REQ消息.】


    1)m_uZoneIndex < KK:每一深度只能從最靠近自己的KK個CRoutingZone可以查找更多的Peer。
    【這個很好懂,如果你理解了前文m_zZoneIndex的含義
    因爲查找Peer很可能會導致CRoutingZone的分裂,如果不控制每一層可分裂的CRoutingZone數目,則回到CRoutingZone以指數方式分裂,導致太多的CRoutingZone。
    【“Peer很可能會導致CRoutingZone的分裂”沒看懂?其實是這個樣子的,儘管這次查找並沒有明確目的,但是一旦查找,返回的信息裏面就有新peer,如果我們查找的目標ID與本地ID太遠,則可能導致有很多較遠的ID被返回(查找過程總是返回與查找目標相近的節點),導致太多的CRz對象產生(這與我們的想法是不同的,那我們到底爲什麼隨機查找呢,其實就是爲了更新peer,越近越好),理解了吧。其實是一個很複雜的過程的,所以一定要搞清楚KAD網絡的基本原理。】


    2)m_uLevel < KBASE:由於有前面的限制,最開始深度的CRoutingZone分裂不會很多,導致較遠的Peer的信息不是很多。使用這個條件可以增多點對較遠Peer的數量的保存。

    【要注意了,前面條件一其實很苛刻的,那麼二其實是有點區別的,它只規定了級別,距離反而可以擴大點。由於條件是並集,因此其實可以利用這個條件調整peer來源】

    3)m_pBin->GetRemaining() >= (K*.8):確保每個葉子CRoutingZone有一些Peer在裏面。
         我剛開始擔心這個會不會導致較遠的CRoutingZone也會不斷的分裂,但是後來發現在分裂前CRoutingZone會調用CanSplit來檢查自己是否可以分裂,使用的判斷條件前兩個和這裏的前兩個是一樣的,這樣就保證了較遠的CRoutingZone不會繼續分解。

    【理解了條件1,2,再看看cansplit的源碼,這個不難理解了】

  9. 在CKademlia::Start裏,會new一個CRoutingZone實例,代表二叉樹的根。CRoutingZone的構造含函數發現自己是根的話,就會從文件中讀取上次保存的聯繫人信息,並且使用這些信息來建立二叉樹。

    【怎麼建呢,看看Process以及裏面的OnBigTimer和OnSmallTimer就知道了】

  10. CRoutingZone的m_tNextSmallTimer是用來定期檢查Peer的狀態的(利用hello_req消息)。每隔1分鐘,OnSmallTimer()就會被運行一次。聯繫人有一個Type,這個Type定義了聯繫人的在線狀態。這個狀態根據聯繫人在在線時間而變化:
    剛剛加入的聯繫人,Type=3;

    【check the function: UpdateType()】

    1) 在線時間小於一個小時的,Type=2;
    2) 在線時間大於一個小時小於兩個小時的,Type=1;
    3) 在線時間大於兩個小時的,Type=0;
    4) 準備刪除的聯繫人,Type=4。

    OnSmallTimer()同樣只是在葉子CRoutingZone裏可以運行。函數每次運行時會從CRoutingZone的聯繫人中找到最老的那個聯繫人,如果那個聯繫人的有效時期已過,並且Type!=4(防止重複檢查)就將向對方發送Request來檢查聯繫人的在線狀態。OnSmallTimer()裏面同樣進行刪除不在線(或者稱爲沒有反應)的聯繫人,那些Type=4並且已經過期了的聯繫人就會被刪除。

    一旦聯繫人回答了請求,他的Type就會被設置爲0~2,而且有效時間被重新加長,這樣在OnSmallTimer()中就不會刪除那些在線的聯繫人。
    一個聯繫人的週期:
    1、剛剛加入:Type=3,有效期=0。OnSmallTimer()將立刻檢查他的情況,同時Type++。
    2、聯繫人回答:Type=2,有效期=1小時;聯繫人沒有回答,下一次OnSmallTimer()會刪除掉它。
    3、1小時後,聯繫人過期,OnSmallTimer()再次發送一個請求,同時Type++,爲3,而不是4。
    4、聯繫人回答:Type=1或2,有效器=1.5小時;聯繫人沒有回答,OnSmallTimer()再次發送Request,Type++,爲4,聯繫人再不回答,就刪除它。



再次Note that:

【1】process每100ms就執行一次;
【2】CKad類對象每10s就onBigTimer一次,用來檢查當前待檢查的CRz對象;(會通過查找一定範圍內的隨機ID來更新Peer)
【3】CRz對象默認1小時開放被檢測;(跟上面不衝突)
【4】OnSmallTimer不是更新peer,而是來檢測Peer狀態,從而調整peer的健康狀態(type).並刪除聯繫不上的或者不滿足條件的。














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