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

以前寫了原理篇,在原理篇裏簡單的介紹了一下DHT,可是還不夠詳細。今天咱們就專門詳細的講一下嗅探器的核心-DHT,這裏默認原理篇你已經讀了。html

背景知識

DHT全稱 Distributed Hash Table,中文翻譯過來就是分佈式哈希表。它是一種去中心化的分佈式系統,特色主要有自動去中心化,強大的容錯能力,支持擴展。另外它規定了本身的架構,包括keyspace和overlay network(覆蓋網絡)兩部分。可是他沒有規定具體的算法細節,因此出現了不少不一樣的實現方式,好比Chord,Pastry,Kademlia等。BitTorrent中的DHT是基於Kademlia的一種變形,它的官方名稱叫作 Mainline DHTnode

DHT人如其名,把它當作一個總體,從遠處看它,它就是一張哈希表,只不過這張表是分佈式的,存在於不少機器上。它同時支持set(key, val),get(key)操做。DHT能夠用於不少方面,好比分佈式文件系統,DNS,即時消息(IM),以及咱們最熟悉的點對點文件共享(好比BT協議)等。git

下面咱們提到的DHT默認都是Mainline DHT,例子都是用僞代碼來表示。讀下面段落的時候要時刻記着,DHT是一個哈希表github

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,今天就說到這裏,關於怎麼樣下載,咱們下篇再說。

你能夠關注個人公衆號,及時得到下一篇推送。

圖片描述

相關文章
相關標籤/搜索