• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            loop_in_codes

            低調(diào)做技術(shù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

            P2P中DHT網(wǎng)絡(luò)爬蟲

            DHT網(wǎng)絡(luò)爬蟲基于DHT網(wǎng)絡(luò)構(gòu)建了一個P2P資源搜索引擎。這個搜索引擎不但可以用于構(gòu)建DHT網(wǎng)絡(luò)中活躍的資源索引(活躍的資源意味著該網(wǎng)絡(luò)中肯定有人至少持有該資源的部分?jǐn)?shù)據(jù)),還可以分析出該網(wǎng)絡(luò)中的熱門分享資源。小蝦不久前發(fā)布了一個這樣的搜索引擎:磁力搜索。他也寫博客對此稍作了介紹:寫了個磁力搜索的網(wǎng)頁 - 收錄最近熱門分享的資源。網(wǎng)絡(luò)上其實(shí)也有其他人做了類似的應(yīng)用:DHT monitoringCrawling Bittorrent DHT

            但是他的這篇文章僅介紹了DHT網(wǎng)絡(luò)的大致工作原理,并且這個爬蟲的具體工作原理也沒有提到。對此我查閱了些文章及代碼,雖然從原理上梳理出了整個實(shí)現(xiàn)方案,但很多細(xì)節(jié)還是不甚清楚。所以本文僅作概要介紹。

            DHT/Magnet/Torrent

            在P2P網(wǎng)絡(luò)中,要通過種子文件下載一個資源,需要知道整個P2P網(wǎng)絡(luò)中有哪些計算機(jī)正在下載/上傳該資源。這里將這些提供某個資源下載的計算機(jī)定義為peer。傳統(tǒng)的P2P網(wǎng)絡(luò)中,存在一些tracker服務(wù)器,這些服務(wù)器的作用主要用于跟蹤某個資源有哪些關(guān)聯(lián)的peer。下載這個資源當(dāng)然得首先取得這些peer。

            DHT的出現(xiàn)用于解決當(dāng)tracker服務(wù)器不可用時,P2P客戶端依然可以取得某個資源的peer。DHT解決這個問題,是因?yàn)樗鼘⒃瓉韙racker上的資源peer信息分散到了整個網(wǎng)絡(luò)中。這里將實(shí)現(xiàn)了DHT協(xié)議的計算機(jī)定義為節(jié)點(diǎn)(node)。通常一個P2P客戶端程序既是peer也是節(jié)點(diǎn)。DHT網(wǎng)絡(luò)有多種實(shí)現(xiàn)算法,例如Kademlia。

            當(dāng)某個P2P客戶端通過種子文件下載資源時,如果沒有tracker服務(wù)器,它就會向DHT網(wǎng)絡(luò)查詢這個資源對應(yīng)的peer列表。資源的標(biāo)識在DHT網(wǎng)絡(luò)中稱為infohash,是一個20字節(jié)長的字符串,一般通過sha1算法獲得,也就是一個類似UUID的東西。

            實(shí)際上,種子文件本身就對應(yīng)著一個infohash,這個infohash是通過種子文件的文件描述信息動態(tài)計算得到。一個種子文件包含了對應(yīng)資源的描述信息,例如文件名、文件大小等。Magnet,這里指的是磁力鏈接,它是一個類似URL的字符串地址。P2P軟件通過磁力鏈接,會下載到一個種子文件,然后根據(jù)該種子文件繼續(xù)真實(shí)資源的下載。

            磁力鏈接中包含的最重要的信息就是infohash。這個infohash一般為40字節(jié)或32字節(jié),它其實(shí)只是資源infohash(20字節(jié))的一種編碼形式。

            Kademlia

            Kademlia是DHT網(wǎng)絡(luò)的一種實(shí)現(xiàn)。網(wǎng)絡(luò)上關(guān)于這個算法的文章,主要是圍繞整個DHT網(wǎng)絡(luò)的實(shí)現(xiàn)原理進(jìn)行論述。個人覺得這些文章很蛋疼,基本上讀了之后對于要如何去實(shí)現(xiàn)一個DHT客戶端還是沒有概念。這里主要可參考P2P中DHT網(wǎng)絡(luò)介紹,以及BitTorrent網(wǎng)站上的DHT協(xié)議描述

            Kad的主要目的是用于查詢某個資源對應(yīng)的peer列表,而這個peer列表實(shí)際上是分散在整個網(wǎng)絡(luò)中。網(wǎng)絡(luò)中節(jié)點(diǎn)數(shù)量很大,如果要獲得peer列表,最簡單的做法無非就是依次詢問網(wǎng)絡(luò)中的每個節(jié)點(diǎn)。這當(dāng)然不可行。所以在Kad算法中,設(shè)立了一個路由表。每一個節(jié)點(diǎn)都有一份路由表。這個是按照節(jié)點(diǎn)之間的距離關(guān)系構(gòu)建出來的。節(jié)點(diǎn)之間的距離當(dāng)然也有特定的算法定義,在Kad中通過對兩個節(jié)點(diǎn)的ID進(jìn)行異或操作得到。節(jié)點(diǎn)的ID和infohash通過相同算法構(gòu)建,都是20字節(jié)長度。節(jié)點(diǎn)和infohash之間也有距離關(guān)系,實(shí)際上表示的是節(jié)點(diǎn)和資源的距離關(guān)系。

            有了這個路由表之后,再通過一個基于距離關(guān)系的查找算法,就可以實(shí)現(xiàn)不用挨個遍歷就找到特定的節(jié)點(diǎn)。而查找資源peer這個操作,正是基于節(jié)點(diǎn)查找這個過程。

            路由表的實(shí)現(xiàn),按我的理解,有點(diǎn)類似一般的hash表結(jié)構(gòu)。在這個表中有160個桶,稱為K桶,這個桶的數(shù)量在實(shí)現(xiàn)上可以動態(tài)增長。每個桶保存有限個元素,例如K取值為8,指的就是這個桶最多保存8個元素。每個元素就是一個節(jié)點(diǎn),節(jié)點(diǎn)包含節(jié)點(diǎn)ID、地址信息以及peer信息。這些桶可以通過距離值索引得到,即距離值會經(jīng)過一個hash算法,使其值落到桶的索引范圍內(nèi)。

            要加入一個DHT網(wǎng)絡(luò),需要首先知道這個網(wǎng)絡(luò)中的任意一個節(jié)點(diǎn)。如何獲得這個節(jié)點(diǎn)?在一些開源的P2P軟件中,會提供一些節(jié)點(diǎn)地址,例如transmission中提供的dht.transmissionbt.com:6881。

            協(xié)議

            Kad定義了節(jié)點(diǎn)之間的交互協(xié)議。這些協(xié)議支撐了整個DHT網(wǎng)絡(luò)里信息分布式存儲的實(shí)現(xiàn)。這些協(xié)議都是使用UDP來傳送。其協(xié)議格式使用一種稱為bencode的編碼方式來編碼協(xié)議數(shù)據(jù)。bencode是一種文本格式的編碼,它還用于種子文件內(nèi)的信息編碼。

            Kad協(xié)議具體格式可參考BitTorrent的定義:DHT Protocol。這些協(xié)議包括4種請求:ping,find_node,get_peer,announce_peer。在有些文檔中這些請求的名字會有不同,例如announce_peer又被稱為store,get_peer被稱為find_value。這4種請求中,都會有對應(yīng)的回應(yīng)消息。其中最重要的消息是get_peer,其目的在于在網(wǎng)絡(luò)中查找某個資源對應(yīng)的peer列表。

            值得一提的是,所有這些請求,包括各種回應(yīng),都可以用于處理該消息的節(jié)點(diǎn)構(gòu)建路由表。因?yàn)槁酚杀肀举|(zhì)就是存儲網(wǎng)絡(luò)中的節(jié)點(diǎn)信息。

            ping

            用于確定某個節(jié)點(diǎn)是否在線。這個請求主要用于輔助路由表的更新。

            find_node

            用于查找某個節(jié)點(diǎn),以獲得其地址信息。當(dāng)某個節(jié)點(diǎn)接收到該請求后,如果目標(biāo)節(jié)點(diǎn)不在自己的路由表里,那么就返回離目標(biāo)節(jié)點(diǎn)較近的K個節(jié)點(diǎn)。這個消息可用于節(jié)點(diǎn)啟動時構(gòu)建路由表。通過find_node方式構(gòu)建路由表,其實(shí)現(xiàn)方式為向DHT網(wǎng)絡(luò)查詢自己。那么,接收該查詢的節(jié)點(diǎn)就會一直返回其他節(jié)點(diǎn)了列表,查詢者遞歸查詢,直到無法查詢?yōu)橹埂D敲矗裁磿r候無法繼續(xù)查詢呢?這一點(diǎn)我也不太清楚。每一次查詢得到的都是離目標(biāo)節(jié)點(diǎn)更接近的節(jié)點(diǎn)集,那么理論上經(jīng)過若干次遞歸查詢后,就無法找到離目標(biāo)節(jié)點(diǎn)更近的節(jié)點(diǎn)了,因?yàn)樽罱墓?jié)點(diǎn)是自己,但自己還未完全加入網(wǎng)絡(luò)。這意味著最后所有節(jié)點(diǎn)都會返回空的節(jié)點(diǎn)集合,這樣就算查詢結(jié)束?

            實(shí)際上,通過find_node來構(gòu)建路由表,以及順帶加入DHT網(wǎng)絡(luò),這種方式什么時候停止在我看來并不重要。路由表的構(gòu)建并不需要在啟動時構(gòu)建完成,在以后與其他節(jié)點(diǎn)的交互過程中,路由表本身就會慢慢地得到構(gòu)建。在初始階段盡可能地通過find_node去與其他節(jié)點(diǎn)交互,最大的好處無非就是盡早地讓網(wǎng)絡(luò)中的其他節(jié)點(diǎn)認(rèn)識自己。

            get_peer

            通過資源的infohash獲得資源對應(yīng)的peer列表。當(dāng)查詢者獲得資源的peer列表后,它就可以通過這些peer進(jìn)行資源下載了。收到該請求的節(jié)點(diǎn)會在自己的路由表中查找該infohash,如果有收錄,就返回對應(yīng)的peer列表。如果沒有,則返回離該infohash較近的若干個節(jié)點(diǎn)。查詢者若收到的是節(jié)點(diǎn)列表,那么就會遞歸查找。這個過程同find_node一樣。

            值得注意的是,get_peer的回應(yīng)消息里會攜帶一個token,該token會用于稍后的announce_peer請求。

            announce_peer

            該請求主要目的在于通知,通知其他節(jié)點(diǎn)自己開始下載某個資源。這個消息用于構(gòu)建網(wǎng)絡(luò)中資源的peer列表。當(dāng)一個已經(jīng)加入DHT網(wǎng)絡(luò)的P2P客戶端通過種子文件開始下載資源時,首先在網(wǎng)絡(luò)中查詢該資源的peer列表,這個過程通過get_peer完成。當(dāng)某個節(jié)點(diǎn)從get_peer返回peer時,查詢者開始下載,然后通過announce_peer告訴返回這個peer的節(jié)點(diǎn)。

            announce_peer中會攜帶get_peer回應(yīng)消息里的token。關(guān)于這一點(diǎn),我有一個疑問是,在P2P中DHT網(wǎng)絡(luò)介紹文檔中提到:

            (announce_peer)同時會把自己的peer信息發(fā)送給先前的告訴者和自己K桶中的k個最近的節(jié)點(diǎn)存儲該peer-list信息

            不管這里提到的K的最近的節(jié)點(diǎn)是離自己最近,還是離資源infohash最近的節(jié)點(diǎn),因?yàn)樘幚韆nnounce_peer消息時,有一個token的驗(yàn)證過程。但是這K個節(jié)點(diǎn)中,并沒有在之前創(chuàng)建對應(yīng)的token。我通過transmission中的DHT實(shí)現(xiàn)做了個數(shù)據(jù)收集,可以證明的是,announce_peer消息是不僅僅會發(fā)給get_peer的回應(yīng)者的。

            DHT爬蟲

            DHT爬蟲是一個遵循Kad協(xié)議的假節(jié)點(diǎn)程序。具體可以參考小蝦發(fā)布的那個網(wǎng)站應(yīng)用:磁力搜索

            這個爬蟲的實(shí)現(xiàn)方式,主要包含以下內(nèi)容:

            • 通過其他節(jié)點(diǎn)的announce_peer發(fā)來的infohash確認(rèn)網(wǎng)絡(luò)中有某個資源可被下載
            • 通過從網(wǎng)絡(luò)中獲取這個資源的種子文件,來獲得該資源的描述

            通過累計收集得到的資源信息,就可以提供一個資源搜索引擎,或者構(gòu)建資源統(tǒng)計信息。以下進(jìn)一步描述實(shí)現(xiàn)細(xì)節(jié)。整個爬蟲的實(shí)現(xiàn)依賴了一個很重要的信息,那就是資源的infohash實(shí)際上就是一個磁力鏈接(當(dāng)然需要包裝一下數(shù)據(jù))。這意味著一旦我們獲得了一個infohash,我們就等于獲得了一個種子。

            獲得資源通知

            當(dāng)爬蟲程序加入DHT網(wǎng)絡(luò)后,它總會收到其他節(jié)點(diǎn)發(fā)來的announce_peer消息。announce_peer消息與get_peer消息里都帶了資源的infohash,但是get_peer里的infohash對應(yīng)的資源在該網(wǎng)絡(luò)中不一定存在,即該資源沒有任何可用peer。而announce_peer則表示已經(jīng)確認(rèn)了該網(wǎng)絡(luò)中有節(jié)點(diǎn)正在下載該資源,也即該資源的數(shù)據(jù)確實(shí)存在該網(wǎng)絡(luò)中。

            所以,爬蟲程序需要盡最大努力地獲取其他節(jié)點(diǎn)發(fā)來的announce_peer消息。如果announce_peer消息會發(fā)送給離消息發(fā)送節(jié)點(diǎn)較近的節(jié)點(diǎn),那么,一方面,爬蟲程序應(yīng)該將自己告訴網(wǎng)絡(luò)中盡可能多的節(jié)點(diǎn)。這可以通過一次完整的find_node操作實(shí)現(xiàn)。另一方面,爬蟲程序內(nèi)部實(shí)現(xiàn)可以部署多個DHT節(jié)點(diǎn),總之目的在于盡可能地讓爬蟲程序稱為其他節(jié)點(diǎn)的較近者。

            當(dāng)收集到infohash之后,爬蟲程序還需要通過該infohash獲得對應(yīng)資源的描述信息。

            獲取資源信息

            獲得資源描述信息,其實(shí)就是通過infohash獲得對應(yīng)的種子文件。這需要實(shí)現(xiàn)P2P協(xié)議里的文件分享協(xié)議。種子文件的獲取其實(shí)就是一個文件下載過程,下載到種子文件之后,就可以獲取到資源描述。這個過程一種簡單的方法,就是從infohash構(gòu)建出一個磁力鏈接,然后交給一個支持磁力下載的程序下載種子。

            從infohash構(gòu)建出磁力鏈接非常簡單,只需要將infohash編碼成磁力鏈接的xt字段即可,構(gòu)建實(shí)現(xiàn)可以從transmission源碼里找到:

            /* 這個算法其實(shí)和printf("%02x", sha1[i])一樣 */
            void tr_sha1_to_hex (char *out, const unsigned char *sha1)
            {
            int i;
            static const char hex[] = "0123456789abcdef";
            for (i=0; i<20; ++i) {
            const unsigned int val = *sha1++;
            *out++ = hex[val >> 4];
            *out++ = hex[val & 0xf];
            }
            *out = '\0';
            }
            void appendMagnet(FILE *fp, const unsigned char *info_hash) {
            char out[48];
            tr_sha1_to_hex(out, info_hash);
            fprintf(fp, "magnet:?xt=urn:btih:%s", out);
            }
            

            現(xiàn)在你就可以做一個實(shí)驗(yàn),在transmission的DHT實(shí)現(xiàn)中,在announce_peer消息的處理代碼中,將收到的infohash通過上面的appendMagnet轉(zhuǎn)換為磁力鏈接輸出到日志文件里。然后,可以通過支持磁力鏈接的程序(例如QQ旋風(fēng))直接下載。有趣的是,當(dāng)QQ旋風(fēng)開始下載該磁力鏈接對應(yīng)的種子文件時,你自己的測試程序能收到QQ旋風(fēng)程序發(fā)出的announce_peer消息。當(dāng)然,你得想辦法讓這個測試程序盡可能地讓其他節(jié)點(diǎn)知道你,這可以通過很多方式實(shí)現(xiàn)。

            總結(jié)

            最終的DHT爬蟲除了需要實(shí)現(xiàn)DHT協(xié)議之外,還需要實(shí)現(xiàn)P2P文件下載協(xié)議,甚至包括一個種子文件解析模塊。看起來包含不小的工作量。而如果要包裝為一個資源搜索引擎,還會涉及到資源存儲以及搜索,更別說前端呈現(xiàn)了。這個時候,如果你使用的語言不包含這些工具庫,那實(shí)在是太悲劇了。沒錯,我就沒找到一個erlang DHT庫(倒是有erlang實(shí)現(xiàn)的BT客戶端,懶得去剝了)。

            UPDATE

            通過詳細(xì)閱讀transmission里的DHT實(shí)現(xiàn),一些之前的疑惑隨之解開。

            announce_peer會發(fā)給哪些節(jié)點(diǎn)

            在一次對infohash的查詢過程中,所有對本節(jié)點(diǎn)發(fā)出的get_peer作出回應(yīng)的節(jié)點(diǎn)(不論這個回應(yīng)節(jié)點(diǎn)回應(yīng)的是nodes還是peers),當(dāng)本節(jié)點(diǎn)取得peer信息時,就會對所有這些節(jié)點(diǎn)發(fā)出announce_peer。get_peer的回應(yīng)消息里,不論是peer還是node,都會攜帶一個token,這樣在將來收到對方的announce_peer時,就可以驗(yàn)證該token。

            節(jié)點(diǎn)和bucket狀態(tài)

            在本地的路由表中,保存的node是有狀態(tài)之分的。狀態(tài)分為三種:good/dubious/bad。good節(jié)點(diǎn)基本可以斷定該節(jié)點(diǎn)是一個在線的并且可以正常回應(yīng)消息的節(jié)點(diǎn);而bad節(jié)點(diǎn)則是可確定的無效節(jié)點(diǎn),通常會盡快從路由表中移除;而dubious則是介于good和bad節(jié)點(diǎn)之間,表示可能有問題的節(jié)點(diǎn),需要進(jìn)一步發(fā)送例如ping消息來確認(rèn)其狀態(tài)。路由表中應(yīng)該盡可能保證保存的是good節(jié)點(diǎn),對查詢消息的回應(yīng)里也需攜帶好的節(jié)點(diǎn)。

            bucket也是有狀態(tài)的,當(dāng)一個bucket中的所有節(jié)點(diǎn)在一定時間之內(nèi)都沒有任何活動的時候,該bucket則應(yīng)該考慮進(jìn)行狀態(tài)的確認(rèn),確認(rèn)方式可以隨機(jī)選擇該bucket中的節(jié)點(diǎn)進(jìn)行find_node操作(這也是find_node除了用于啟動之外的唯一作用,但具體實(shí)現(xiàn)不見得使用這種方式)。沒有消息來往的bucket則應(yīng)該考慮移除。DHT中幾乎所有操作都會涉及到bucket的索引,如果索引到一個所有節(jié)點(diǎn)都有問題的bucket,那么該操作可能就無法完成。

            search在何時停止

            首先,某次發(fā)起的search,無論是對node還是對peer,都可能導(dǎo)致進(jìn)一步產(chǎn)生若干個search。這些search都是基于transaction id來標(biāo)識的。由一次search導(dǎo)致產(chǎn)生的所有子search都擁有相同的transaction id,以使得在該search成功或失敗時可以通過該transaction id來刪除對應(yīng)的所有search。transaction id也就是DHT中每個消息消息頭”t”的值。

            但是search何時停止?transmission中是通過超時機(jī)制來停止。在search過程中,如果長時間沒有收到跟該search關(guān)聯(lián)的節(jié)點(diǎn)發(fā)來的回應(yīng)消息,那么就撤銷該search,表示搜索失敗。

            參考資料

            posted on 2013-05-19 21:51 Kevin Lynx 閱讀(13784) 評論(0)  編輯 收藏 引用 所屬分類: network

            国产精品禁18久久久夂久| 国产精品禁18久久久夂久 | 国产午夜精品久久久久九九电影| 97久久香蕉国产线看观看| 国产激情久久久久影院小草| 伊人久久成人成综合网222| 久久久精品2019免费观看| 久久国产精品免费一区| 亚洲国产精品无码久久98| 99久久精品久久久久久清纯| 午夜精品久久久内射近拍高清| jizzjizz国产精品久久| 亚洲国产成人久久精品99| 99久久人人爽亚洲精品美女| 亚洲国产另类久久久精品黑人| 久久夜色精品国产亚洲av| 韩国无遮挡三级久久| 久久精品日日躁夜夜躁欧美| 91久久精品电影| 久久伊人精品青青草原高清| 国产毛片欧美毛片久久久| 深夜久久AAAAA级毛片免费看| 丁香五月综合久久激情| www.久久热| 99久久人妻无码精品系列| 亚洲中文字幕久久精品无码喷水| 亚洲国产精品成人久久蜜臀| 国产精品无码久久久久| 国产成人99久久亚洲综合精品| 亚洲色欲久久久综合网| 久久久久久国产a免费观看黄色大片| 国产一区二区三精品久久久无广告 | 久久亚洲精品成人AV| 欧美精品乱码99久久蜜桃| 午夜精品久久久久久久无码| 色婷婷久久综合中文久久一本| 久久精品无码一区二区app| 久久精品国产亚洲av瑜伽| 久久久久97国产精华液好用吗| 国产日韩欧美久久| 国产精品久久久久久久久久影院|