<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 56,  comments - 12,  trackbacks - 0

    本篇文章分析 Tracker 類,它在 track.py 文件中。

    在分析之前,我們把前幾篇文章的內容再回顧一下,以理清思路。

     

    BT 的源碼,主要可以分為兩個部分,一部分用來實現 tracker 服務器,另一部分用來實現 BT 的客戶端。我們這個系列的文章圍繞 tracker 服務器的實現來展開。

    BT 客戶端與 tracker 服務器之間,通過 track  HTTP 協議 進行通信,而 BT 客戶端之間以 BT 對等協議 進行通信。

    Tracker 服務器的職責是搜集客戶端的信息,并幫助客戶端相互發現對方,從而使得客戶端之間能夠相互建立連接,進而互相能下載所需的文件片斷。

     

    在實現 tracker 服務器的時候,首先是通過 RawServer 類來實現網絡服務器的功能,然后由 HTTPHandler 類來完成對協議數據的第一層分析。因為 track  HTTP 協議是以 HTTP 協議的形式交互的,所以 HTTPHandler 按照 HTTP 的協議對客戶端的請求進行第一層處理(也就是取得 URL HTTP 消息頭),然后把 URL HTTP 消息頭進一步交給 Tracker 類來進行第二層分析,并把分析的結果按照 HTTP 協議的格式封裝以后,發給客戶端。

    Tracker 類對 track HTTP 協議做第二層分析,它根據第一層分析后的 URL 以及 HTTP 消息頭,進一步得到客戶端的信息(包括客戶端的 ip 地址、端口、已下載完的數據以及剩余數據等等),然后綜合當前所有下載者的情況,生成一個列表,這個列表記錄了下載同一個文件的其它下載者的信息(但不是所有的下載者,只是選擇一部分),并把這個列表交給 HTTPHandler ,由它進一步返回給客戶端。

    如此,整個 tracker 服務器的實現,在層次上就比較清晰了。

     

           為了分析 Tracker 類,首先要理解“狀態文件”。

          

    l         狀態文件:

    在第一篇文章中,我們說到,要啟動一個 tracker 服務器,至少要指定一個參數,就是狀態文件。在 Tracker 的初始化函數中,主要就是讀取指定的狀態文件,并根據該文件做一些初始化的工作。所以必須弄清楚狀態文件的作用:

     

    1.         狀態文件的作用:

    tracker 服務器如果因為某些意外而停止,那么所有的下載者不僅不能繼續下載,而且先前所做的努力都前功盡棄。這種情況是不能容忍的,因此,必須保證在 tracker 重新啟動之后,所有的下載者還能繼續工作。 Tracker 服務器周期性的將當前系統中必要的下載狀態信息保存到狀態文件中,在它因故停止,而后又重新啟動的時候,可以根據這些信息重新恢復“現場”,從而使得下載者可以繼續下載。

     

    2.         狀態文件的格式:

    狀態文件的信息對應著一個比較復雜的 4 級嵌套的字典。

     

    要詳細分析這個字典類型,必須理解一點:一個 tracker 服務器,可以同時為下載不同文件的幾批下載者提供服務。

    我們知道,一批下載同一個文件的下載者,它們必然擁有同樣的 torrent 文件,它們能根據 torrent 文件找到同一個 tracker 服務器。而下載另一個文件的一批下載者,必然擁有另外一個 torrent 文件,但是這兩個不同的 torrent 文件,可能指向的是同一個 tracker 服務器。所以說“一個 tracker 服務器,可以同時為下載不同文件的幾批下載者提供服務。”

    實際上,那些專門提供 bt 下載的網站,都是架設了一些專門的 tracker 服務器,每個服務器可以同時為多個文件提供下載跟蹤服務。

    理解了這一點,我們繼續分析狀態文件的格式。

     

    第一級字典:

    Tracker 的初始化函數中,有這樣的代碼,

     

    if exists(self.dfile):

    h = open(self.dfile, 'rb')

    ds = h.read()

    h.close()

    tempstate = bdecode(ds)

    else:

    tempstate = {}

     

           這段代碼是從從狀態文件中讀取信息,由于讀到的是經過 Bencoding 編碼后的數據,所以還需要經過解碼,解碼后就得到一個字典類型的數據,保存到 template 中,這就是第一級字典。 它有兩個關鍵字, peers completed ,分別用來記錄參與下載的 peer 的信息和已經完成了下載的 peer 的信息(凡是出現在 completed peer ,也必然出現在 peers 中)。這兩個關鍵字對應的數據類型都是字典,我們重點分析 peers 關鍵字所對應的第二級字典。

     

    第二級字典:

    關鍵字: torrent 文件中 info 部分的 SHA hash

    數據:第三級字典

     

    一個被下載的文件,唯一的被一個 torrent 文件標識, tracker 通過計算 torrent 文件中 info 部分的 SHA hash ,這是一個 20 字節的字符串,它可以唯一標識被下載文件的信息。第二級字典以此字符串作為關鍵字,保存下載此文件的下載者們的信息。

     

    第三級字典:

    關鍵字:下載者的 peer id

    數據:第四級字典

     

    解釋:每個下載者,都創建一個唯一標識自己的 20 字節的字符串,稱為 peer id 。第三級字典以次為關鍵字,保存每個下載者的信息。

     

    第四級字典:

    關鍵字: ip port left

    數據:分別保存下載者的 ip 地址、端口號和未下載完成的字節數

    另外還有兩個可選的關鍵字 given ip nat ,它們是用于 NAT 的,關于 NAT 的情況,后面會再提到。

     

    理解了這個 4 級嵌套的字典,對 Tracker 的分析才好繼續進行下去。

     

    下面我們挨個看 Tracker 類的成員函數。

     

    l         初始化函數 __init__()

     

    開始是一些參數的初始化,其中比較難理解的有:

     

    self.response_size = config['response_size']

    self.max_give = config['max_give']

     

    要理解這兩個參數,必須看那份更詳細的 BT 協議規范中對“ numwant ”關鍵字的解釋:

    · numwant : Optional. Number of peers that the client would like to receive from the tracker. This is permitted to be zero. If omitted, typically defaults to 50 peers.

    If a client wants a large peer list in the response, then it should specify the numwanted parameter.

     

    意思就是說,默認情況下, tracker 服務器給下載者響應的 peers 個數是 response_size 個,但有時候,下載者可能希望獲得更多的 peers 信息,那么它必須在請求中包含 numwant 關鍵字,并指定希望獲得 peers 的個數。例如是 300 tracker 300 max_give 中較小的一個,作為返回給下載者的 peers 的個數。

     

     

    self.natcheck = config['nat_check']

    self.only_local_override_ip = config['only_local_override_ip']

     

    這兩個參數是和 NAT 相關的,我們終于必須要說到 NAT 了。

    我們知道,如果一個 BT 客戶端處在局域網中,通過 NAT 之后連到 tracker 服務器的話,那么 tracker 服務器從連接中獲得的該客戶端的 IP 地址是一個公網 IP ,如果其它客戶端通過這個 IP 試圖連接該客戶端的話,肯定會被 NAT 拒絕的。

    通過一些 NAT 穿越的技術,在某些情況下,可以讓一些客戶端穿過 NAT ,與處在局域網中的客戶端建立連接,具體的技術資料我已經貼在論壇上了,大家有興趣可以去看一看。原來我以為 BT 也用到了一些 NAT 穿越技術,但現在發現并沒有,可能是技術實現上比較復雜,而且不能保證在任何情況下都有效的原因吧。

     

    我們來看那份比較詳細的協議規范中,對“ ip ”關鍵字的解釋:

    · ip : Optional. The true IP address of the client machine, in dotted quad format. Notes: In general this parameter is not necessary as the address of the client can be determined from the IP address from which the HTTP request came. The parameter is only needed in the case where the IP address that the request came in on is not the IP address of the client. This happens if the client is communicating to the tracker through a proxy (or a transparent web proxy/cache.) It also is necessary when both the client and the tracker are on the same local side of a NAT gateway. The reason for this is that otherwise the tracker would give out the internal (RFC1918) address of the client, which is not routeable. Therefore the client must explicitly state its (external, routeable) IP address to be given out to external peers. Various trackers treat this parameter differently. Some only honor it only if the IP address that the request came in on is in RFC1918 space. Others honor it unconditionally, while others ignore it completely.

     

    在客戶端發給 tracker 服務器的請求中,可能包含“ ip ”,也就是指定自己的 IP 地址。你可能有疑問了,客戶端為什么要通知 tracker 服務器自己的 ip 地址了? tracker 服務器完全可以從連接中獲得這個 ip 啊。嗯,實際的網絡情況是非常復雜的,如果客戶端是在局域網內通過 NAT 后上網,或者客戶端是通過某個代理服務器之后,再與 tracker 服務器建立連接,那么 tracker 從連接中獲得的 ip 地址并不是客戶端真實的 ip 地址,為了獲得真實的 ip ,必須讓客戶端主動在協議中通知 tracker 。因此,就出現了兩個 ip 地址,一個是從連接中獲得的 ip 地址,我把它叫做“連接 ip ”,另一個是客戶端通過請求傳遞過來的 ip ,我叫它“真實 ip ”。顯然, tracker 應該把客戶端的“真實 ip ”記錄下來,并把這個“真實 ip ”通知給其它下載者。

    這個“ ip ”參數又是可選的,也就是說,如果客戶端擁有一個公網的 ip ,而且并沒有通過 NAT 或者代理,那么,它并不需要傳遞這個參數,“連接 ip ”就是“真實 ip ”。

    按協議規發的說法,“ ip ”這個參數在以下兩種情況下有用:

    1 、客戶端可能擁有一個公網 IP ,但它又是通過一個代理服務器與 tracker 服務器建立連接的,它需要傳遞“ ip ”。

    2 、客戶端在某個局域網中,恰好 tracker 也在同一個局域網中,。。。(這種情況又會怎么樣了?我還沒有弄明白 :)

     

     

    回過頭來看 natcheck only_local_override_ip

    natcheck how many times to check if a downloader is behind a NAT (0 = don't check)

    only_local_override_ip 如果從 GET 參數中傳遞過來的 ip ,是一個公網 ip ,是否忽略它?它的默認值是 1

    現在還不好理解它的意思,我們看后面代碼的時候,再來理解它。

     

     

    self.becache1 = {}

    self.becache2 = {}

    self.cache1 = {}

    self.cache2 = {}

    self.times = {}

     

    這里出現 5 個字典,其中 times 用來,而其它 4 個字典的作用是什么?

    嗯,還是讓我們先來看看在“ BT 移植郵件列表”中, Bram Cohen 發的一個帖子,

     

    There are two new GET parameters for the tracker in the latest release. They are –

     

    key=xxxx - this is like peer id, but it's only known to the client and the tracker. It allows clients to be behind dynamic IP. If a peer announced a key previously, then it's accepted if and only if it gives the same key again. If no key was given, then the fallback is checking that the IP hasn't changed. If the IP has changed, mainline currently will give a peer list but not change any data related to that peer, so that peers behind dynamic IP using old clients will continue to work okay. Currently mainline generates the associated with key as eight random hex s, and the tracker accepts any string from clients.

     

    compact=1 - when a client sends this, the 'peers' return is a single string whose length is a multiple of 6 rather than a dict. To extract peer information from the string, chop it into substrings of length 6. For each substring, the first four bytes are the IP and the last two are the port, encoded big-endian. This results in huge bandwidth savings.

     

    Everybody developing ports should implement these keys, they're very useful.

     

    -Bram

     


          

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    BT 在不停的向前發展,所以協議規范也在發展之中,新引入了兩個關鍵字,其中一個是 compact ,如果客戶端請求中 compact=1 ,表示緊湊模式,也就是 tracker 給客戶端響應的數據,采用一種比原來更緊湊的形式,這樣可以有效的節約帶寬。

    Becache1 cache1 用于普通模式,而 becache2 cache2 用于緊湊模式。我們馬上能看到它們的初始化操作。

     

    if exists(self.dfile):

    h = open(self.dfile, 'rb')

    ds = h.read()

    h.close()

    tempstate = bdecode(ds)

    else:

    tempstate = {}

     

    if tempstate.has_key('peers'):

    self.state = tempstate

    else:

    self.state = {}

    self.state['peers'] = tempstate

    self.downloads = self.state.setdefault('peers', {})

    self.completed = self.state.setdefault('completed', {})

    statefiletemplate(self.state)

     

    這部分代碼是讀取狀態文件,初始化 downloads completed 這兩個字典,并檢查讀取的數據是否有效。

    現在, downloads 里面是保存了所有下載者的信息,而 completed 保存了所有完成下載的下載者的信息。

     

    for x, dl in self.downloads.items():

    self.times[x] = {}

            for y, dat in dl.items():

                   self.times[x][y] = 0

                if not dat.get('nat',1):

                       ip = dat['ip']

                    gip = dat.get('given ip')

                    if gip and is_valid_ipv4(gip) and (not self.only_local_override_ip or is_local_ip(ip)):

                           ip = gip

    self.becache1.setdefault(x,{})[y] = Bencached(bencode({'ip': ip, 'port': dat['port'], 'peer id': y}))

                    self.becache2.setdefault(x,{})[y] = compact_peer_info(ip, dat['port'])

     

    這里,對 times becache1 becache2 初始化。它們都是 2 級嵌套的字典,第一級的關鍵字是 torrent 文件中的 info 部分的 hash ,第二級關鍵字是下載者的 peer id becache1 保存的是一個 Bencached 對象,而 becache2 保存的是一個字符串,它是把 ip port 組合成的一個字符串。

     

    參數設置完之后,有:

    rawserver.add_task(self.save_dfile, self.save_dfile_interval)

     

    add_task() 我們已經見到過好多次了,這表示每隔一段時間,需要調用 save_dfile() 來保存狀態文件。

     

    再后面的代碼,我沒有仔細看了,象 allow_get allowed_dir 等的意義,還需要看相關的代碼才能明白,如果你仔細看了這些部分,希望能補充一下。

     

    初始化以后,就是 Tracker 的最重要,也是代碼最長的函數: get()

     

    l         get()

     

    在第三篇文章中,我們已經看到,在由 HTTPHandler track HTTP 協議進行第一層分析之后,就是調用 Tracker::get() 來進行第二層分析的。它的參數是 URL HTTP 消息頭。

     

    在這個函數中,首先調用 urlparse() URL 進行解析,例如這樣的 URL

     

    /announce?ip=192.168.112.1&port=9999&left=2000

     

    解析之后,就獲得了 path ,是 announce ,還有參數,包括:

    ip 192.168.112.1

    port 9999

    left 2000

     

    然后,根據 path 的不同,分別處理。

     

    一般來說,客戶端發給 tracker 的請求中, path 都是 announce ,但有時候,第三方可能也想查詢一下 tracker 服務器的狀態,那么它可以通過其它的 path 來向 tracker 服務器請求,例如 scrape 。在一些專門提供 bt 下載的網站上,我們可以看到不停更新的下載者、種子個數等信息,就是用這種方式從 tracker 服務器處獲得的。

    我們只看 path announce 的情況。

     

    首先是對客戶端傳遞來的參數的有效性進行檢查,包括是不是有 info_hash 關鍵字? ip 地址是否合法等等。

     

    然后,

    ip = connection.get_ip()

    這樣得到的 ip ,是根據客戶端與 tracker 服務器建立的連接中獲取的 ip ,就是“連接 ip ”了。

     

    接下來,

    ip_override = 0

    if params.has_key('ip') and is_valid_ipv4(params['ip']) and (not self.only_local_override_ip or is_local_ip(ip)):

    ip_override = 1

          

    這段代碼的意圖,是為了判斷在隨后保存客戶端的 ip 地址的時候,是否要用“真實 ip ”來取代“連接 ip ”。如果 ip_override 1 ,那么就保存“真實 ip ”,也就是“連接 ip ”被“真實 ip ”覆蓋( override )了。

     

    分析源碼的過程其實就是揣測作者意圖的過程,我的揣測是這樣的:

    如果客戶端從請求中傳遞了“真實 ip ”,那么對 tracker 來說,,既然客戶端都已經報告了“真實 ip ”了,那么當然就保存“真實 ip ”就好了。可如果“真實 ip ”是個公網 ip ,而且 only_local_override_ip=1 ,也就是說,忽略“真實 ip ”為公網 ip 的情況,那么,保存的是“連接” ip

    說句實話,為什么要設置 only_local_override_ip 這么一個參數,我還是沒有弄明白。

     

    if peers.has_key(myid):

    myinfo = peers[myid]

    if myinfo.has_key('key'):

    if params.get('key') != myinfo['key']:

                      return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'},

                            bencode({'failure reason': 'key did not match key supplied earlier'}))

            confirm = 1

    elif myinfo['ip'] == ip:

               confirm = 1

    else:

           confirm = 1

     

    這段代碼涉及到身份驗證吧,我沒有仔細看了,關于 key ”的解釋,請看上面 Bram Cohen 的帖子。

     

    接下來,如果驗證通過,而且事件不是“ stopped ”,那么就把客戶端的信息保存下來。如果已經存在該客戶端的信息,那么就更新一下。注意這里 ip_override 派上了用場,也就是如果覆蓋,那么保存的是“真實 ip ”,否則保存的是“連接 ip ”。

     

    if port == 0:

    peers[myid]['nat'] = 2**30

    elif self.natcheck and not ip_override:

    to_nat = peers[myid].get('nat', -1)

    if to_nat and to_nat < self.natcheck:

    NatCheck(self.connectback_result, infohash, myid, ip, port, self.rawserver)

           else:

    peers[myid]['nat'] = 0

     

           第一個 port == 0 的情況,不知道是什么意思?

    第二個表示要檢查 NAT 的情況。大概意思就是 tracker 服務器主動用 BT 對等協議與該客戶端進行握手,如果握手成功,那么說明該客戶端是可以被直接連接的。這一點很重要,如果 tracker 服務器無法和客戶端直接建立連接的話,那么其它下載者也無法和該客戶端建立連接。

    這里用到的 NatChecker 類,也是一個 Handler 類,具體細節,大家自己分析吧。

     

    data = {'interval': self.reannounce_interval} 

     

    從這到最后,就是根據緊湊模式和普通模式兩種不同情況,分別從 becache1 或者 becache2 中,返回隨機的 peers 的信息。

     

    在這里,我們來總結一下 cache1 becache1 cache2 becache2 的用處。我感覺 cache1 cache2 好像沒什么作用,因為從代碼中沒有看到它們兩的意義。 Becache1 becache2 則分別用于普通模式和緊湊模式情況下,對 peers 的信息進行緩存。它們從狀態文件中初始化自己;如果有新的 peer 出現,被添加到這兩個緩存中;如果是“ stopped ”事件,那么從緩存中刪除對應的 peer 。最后, tracker 根據情況,從其中一個緩存取得隨機的 peers 的信息,返回給客戶端。

     

    l         connectback_result()

    這個函數,用于 NatCheck 類作為回調函數。它根據 tracker 服務器主動與客戶端建立連接的結果做一些處理。其中的參數 result ,是表示 tracker 與客戶端建立連接是否成功。如果建立成功,顯然對方不在 NAT 后面,否則就是在 NAT 后面了。 record['nat'] += 1 這沒看懂,為什么不是直接 record['nat'] = 1 ?最后,如果建立連接成功,那么更新一下 becache1 becache2

     
    posted on 2007-01-19 00:18 苦笑枯 閱讀(455) 評論(0)  編輯  收藏 所屬分類: P2P
    收藏來自互聯網,僅供學習。若有侵權,請與我聯系!

    <2007年1月>
    31123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    常用鏈接

    留言簿(2)

    隨筆分類(56)

    隨筆檔案(56)

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲国产亚洲片在线观看播放 | 91亚洲自偷在线观看国产馆| 手机看黄av免费网址| 午夜网站在线观看免费完整高清观看| 久久久久亚洲AV片无码下载蜜桃| 国产三级在线观看免费| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 亚洲成a人在线看天堂无码| a国产成人免费视频| 亚洲fuli在线观看| 国产日产亚洲系列最新| 100000免费啪啪18免进| 亚美影视免费在线观看| 亚洲精品中文字幕无乱码| 亚洲成人影院在线观看| 67194成手机免费观看| 大桥未久亚洲无av码在线 | 久久久久久久久久久免费精品| 亚洲日产2021三区在线 | 亚洲色大成网站www| 亚洲精品夜夜夜妓女网| 国产精品高清全国免费观看| a毛片久久免费观看| 亚洲第一综合天堂另类专| 亚洲四虎永久在线播放| 免费在线视频一区| 一个人免费观看视频www| 久久免费视频观看| 一级A毛片免费观看久久精品| 456亚洲人成影院在线观| 亚洲AV午夜成人影院老师机影院| 国产成人高清精品免费鸭子| h在线观看视频免费网站| 97国免费在线视频| 青青久久精品国产免费看| 亚洲国产精品一区二区第四页| 成人黄色免费网站| 久久免费公开视频| 老司机精品免费视频| 免费国产va在线观看| 亚洲日韩精品无码专区|