一步一步教你寫BT種子嗅探器之二---DHT篇

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

之前寫了原理篇,在原理篇裡簡單的介紹了一下DHT,但是還不夠詳細。今天我們就專門詳細的講一下嗅探器的核心-DHT,這裡預設原理篇你已經讀了。

背景知識

DHT全稱 Distributed Hash Table,中文翻譯過來就是分布式雜湊表。它是一種去中心化的分布式系統,特點主要有自動去中心化,強大的容錯能力,支援擴充。另外它規定了自己的架構,包括keyspace和overlay network(覆蓋網路)兩部分。但是他沒有規定具體的演算法細節,所以出現了很多不同的實現方式,比如Chord,Pastry,Kademlia等。BitTorrent中的DHT是基於Kademlia的一種變形,它的官方名稱叫做 Mainline DHT。

DHT人如其名,把它看成一個整體,從遠處看它,它就是一張雜湊表,只不過這張表是分布式的,存在於很多機器上。它同時支援set(key, val),get(key)操作。DHT可以用於很多方面,比如Distributed File System,DNS,立即訊息(IM),以及我們最熟悉的點對點檔案分享權限設定(比如BT協議)等。

下面我們提到的DHT預設都是Mainline DHT,例子都是用虛擬碼來表示。讀下面段落的時候要時刻記著,DHT是一個雜湊表

Mainline DHT

Mainline DHT遵循DHT的架構,下面我們分別從Keyspace和Overlay network兩方面具體說明。

Keyspace

keyspace主要是關於key的一些規定。

Mainline dht裡邊的key長度為160bit,注意是bit,不是byte。在常見的編譯型程式設計語言中,最長的整型也才是64bit,所以用整型是表示不了key的,我們得想其他的方式。我們可以用數組方式表示它,數群組類型你可以選用長度不同的整型,比如int8,int16,int32等。這裡為了下邊方便計算,我們採用長度為20的byte數組來表示。

在mainline dht中,key之間唯一的一種計算是xor,即異或(還記得異或的知識吧?)。我們的key是用長度為20的byte數組來表示,因此我們應該從前往後依次計算兩個key的相對應的byte的異或值,最終結果得到的是另外一個長度為20的byte數組。演算法如下:

for i = 0; i < 20; i++ {    result[i] = key1[i] ^ key2[i];}

讀到這裡,你是不是要問xor有啥用?還記得原理篇中DHT的工作方式嗎?

xor是為了找到好友表中離key最近的k個節點,什麼樣的節點最近?就是好友中每個節點和key相異或,得到的結果越小就越近。這裡又衍生另外一個問題,byte數組之間怎麼比較大小?很簡單,從前往後,依次比較每一個byte的大小即可。

在Mainline DHT中,我們用160bit的key來代表每個節點和每個資源的ID,我們尋找節點或者尋找資源的時候實際上就是尋找他們的ID。回想一下,這是不是很雜湊表? :)

另外聰明的你可能又該問了,我們怎麼樣知道每個節點或者每個資源的ID是多少?在Mainline DHT中,節點的ID一般是隨機產生的,而資源的ID是用sha1演算法加密資源的內容後得到的。

OK,關於key就這麼多,代碼實現你可以查考這裡。

Overlay network

Overlay network主要是關於DHT內部節點是怎麼儲存資料的,不同節點之間又是怎樣通訊的。

首先我們回顧一下原理篇中DHT的工作方式:

DHT 由很多節點群組成,每個節點儲存一張表,表裡邊記錄著自己的好友節點。當你向一個節點A查詢另外一個節點B的資訊的時候,A就會查詢自己的好友表,如果裡邊包含B,那麼A就返回B的資訊,否則A就返回距離B距離最近的k個節點。然後你再向這k個節點重新查詢B的資訊,這樣迴圈一直到查詢到B的資訊,查詢到B的資訊後你應該向之前所有查詢過的節點發個通知,告訴他們,你有B的資訊。

整個DHT是一個雜湊表,它把自己的資料化整為零分散在不同的節點裡。OK,現在我們看下,一個節點內部是用什麼樣的資料結構儲存資料的。

節點內部資料存放區 - Routing Table

用什麼樣的資料結構得看支援什麼樣的操作,還得看各種操作的頻繁程度。從上面工作方式我們知道,操作主要有兩個:

  • 在我(注意:“我”是一個節點)的好友節點中查詢離一個key最近的k個節點(在Mainline DHT中,k=8),程度為頻繁

  • 把一個節點儲存起來,也就是插入操作,程度為頻繁

首先看到“最近”、“k”,我們會聯想到top k問題。一個很straightforward的做法是,用一個數組儲存節點。這樣的話,我們看下演算法複雜度。top k問題用堆解決,查詢複雜度為O(k + (n-k)*log(k)),當k=8時,接近於O(n);插入操作為O(1)。註:n為一個節點的好友節點總數。

當n很大的時候,操作時間可能會很長。那麼有沒有O(log(n))的演算法呢?

聯想到上面堆的演算法,你可能說,我們可以維護一個堆啊,插入和查詢的時候都是O(log(n))。這種做法堆是根據堆中元素與某一個固定不變的key的距離來維護的,但是通常情況下,我們查詢的key都是變化的,因此這種做法不可行。

那還有其他O(log(n))的演算法嗎?

經驗告訴我們,很多O(log(n))的問題都和二叉樹相關,比如各種平衡二叉樹,我們能不能用二叉樹來解決呢?聯想到每個ID都是一個160bit的值,而且我們知道key之間的距離是通過異或來計算的,並且兩個key的異或結果大小和他們的共同首碼無關,我們應該想到用Trie樹(或者叫首碼樹)來解決。事實上,Mainline DHT協議中用的就是Trie樹,但是與Trie樹又稍微有所不同。在Trie樹裡邊,插入一個key時,我們要比對key的每一個char和Trie裡邊路徑,當不一致時,會立刻分裂成一個子樹。但是在這裡,當不一致時,不會立刻分裂,而是有一個長度為k的buffer(在Mainline DHT中叫bucket)。分兩種情況討論:

  • 如果bucket長度小於k,那麼直接插入bucket就行了。

  • 如果bucket長度大於或等於k,又要分兩種情況討論:

    • 第一種情況是當前的路徑是該節點ID(注意不是要插入的key,是“我”自己的ID)的首碼,那麼就分裂,左右子樹的key分別是0和1,並且把當前bucket中的節點根據他們的當前char值分到相應的子樹的bucket裡邊。

    • 第二種情況是當前路徑不是該節點ID的首碼,這種情況下,直接把這個key丟掉。

如果還沒有理解,你可以參照Kademlia這篇論文上面的圖。

插入的時候,複雜度為O(log(n))。查詢離key最近的k個節點時,我們可以先找到當前key對應的bucket,如果bucket裡邊不夠k個,那麼我們再尋找該節點前驅和後繼,最後根據他們與key的距離拍一下序即可,平均複雜度也為O(log(n))。這樣插入和查詢都是O(log(n))。

代碼實現你可以查考這裡。

節點之間的通訊 - KRPC

KRPC比較簡單,它是一個簡單的rpc結構,其是通過UDP傳送訊息的,報文是由bencode編碼的字典。它包含3種訊息類型,request、response和error。請求又分為四種:ping,find_node, get_peers, announce_peer。

  • ping 用來偵探對方是否線上

  • find_node 用來尋找某一個節點ID為Key的具體資訊,資訊裡包括ip,port,ID

  • get_peers 用來尋找某一個資源ID為Key的具體資訊,資訊裡包含可提供下載該資源的ip:port列表

  • announce_peer 用來告訴別人自己可提供某一個資源的下載,讓別人把這個訊息儲存起來。還記得Angelababy那個例子嗎?在我得到她的號後,我會通知所有我之前問過的人,他們就會把我有Angelababy號這個資訊儲存起來,以後如果有人再問他們有沒有Angelababy號的話,他們就會告訴那個人我有。BT種子嗅探器就是根據這個來得到訊息的,不過得到訊息後我們還需要進一步下載

跳出節點,整體看DHT這個雜湊表,find_node和get_peers就是我們之前說的get(key),announce_peer就是set(ke, val)。

剩下的就是具體的訊息格式,你可以在官方文檔上看到,這裡就不搬磚了。

實現KRPC時,需要注意的有以下幾點:

  • 每次收到請求或者回複你都需要根據情況更新你的Routing Table,或儲存或丟掉。

  • 你需要實現transaction,transaction裡邊要包含你的請求資訊以及被請求的ip及連接埠,只有這樣當你收到回複訊息時,你才能根據訊息的transaction id做出正確的處理。Mainline DHT對於如何?transaction沒有做具體規定。

  • 一開始你是不在DHT網路中的,你需要別人把你介紹進去,任何一個在DHT中的人都可以。一般我們可以向 router.bittorrent.com:6881dht.transmissionbt.com:6881 等發送find_node請求,然後我們的DHT就可以開始工作了。

KRPC的實現你可以參考這裡。

總結

DHT整體就是一張雜湊表,首先我們本身是裡邊的一個節點,我們向別人發送krpc find_node或get_peers訊息,就是在對這個雜湊表執行get(key)操作。向別人發送announce_peer訊息,就是在對這個雜湊表執行set(key, val)操作。

最後

https://github.com/shiyanhui/dht 完整代碼在這裡,喜歡這篇文章的話就到github上給個Star唄 :)

http://bthub.io 是基於上面這個嗅探器寫的一個BT種子搜尋引擎。

有任何問題可以在這裡提問:https://github.com/shiyanhui/dht/issues

OK,今天就說到這裡,關於怎麼樣下載,我們下篇再說。

你可以關注我的公眾號,及時獲得下一篇推送。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.