tracker服務器是BT下載中必須的角色。一個BT client 在下載開始以及下載進行的過程中,要不停的與 tracker 服務器進行通信,以報告自己的信息,並獲取其它下載client的信息。這種通信是通過 HTTP 協議進行的,又被稱為 tracker HTTP 協議,它的過程是這樣的:
client 向 tracker 發一個HTTP 的GET請求,並把它自己的信息放在GET的參數中;這個請求的大致意思是:我是xxx(一個唯一的id),我想下載yyy文件,我的ip是aaa,我用的端口是bbb。。。
tracker 對所有下載者的信息進行維護,當它收到一個請求后,首先把對方的信息記錄下來(如果已經記錄在案,那么就檢查是否需要更新),然后將一部分(並非全部,根據設置的參數已經下載者的請求)參與下載同一個文件(一個tracker服務器可能同時維護多個文件的下載)的下載者的信息返回給對方。
Client在收到tracker的響應后,就能獲取其它下載者的信息,那么它就可以根據這些信息,與其它下載者建立連接,從它們那里下載文件片斷。
關於client和tracker之間通信協議的細節,在“BT協議規范”中已經給出,這里不再重復。下面我們具體分析 tracker服務器的實現細節。
從哪里開始?
要建立一個 tracker服務器,只要運行 bttrack.py 程序就行了,它最少需要一個參數,就是 –dfile,這個參數指定了保存下載信息的文件。Bttrack.py 調用 track.py 中的 track()函數。因此,我們跟蹤到 track.py 中去看track() 函數。
Track.py:track()
這個函數首先對命令行的參數進行檢查;然后將這些參數保存到 config 字典中。在BT中所有的工具程序,都有類似的處理方式。
接下來的代碼:
- r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout'])
- t = Tracker(config, r)
- r.bind(config['port'], config['bind'], True)
- r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))
- t.save_dfile()
-
首先是創建一個 RawServer 對象,這是一個服務器對象,它將實現一個網絡服務器的一些細節封裝起來。不僅tracker服務器用到了 RawServer,我們以后還可以看到,由於每個 client端也需要給其它 client 提供下載服務,因此也同時是一個服務器,client的實現中,也用到了RawServer,這樣,RawServer的代碼得到了重用。關於 RawServer的詳細實現,在后面的小節中進行分析。
接着是創建一個 Tracker對象。
然后讓RawServer綁定在指定的端口上(通過命令行傳遞進來)。
最后,調用 RawServer::listen_forever() 函數,使得服務器投入運行。
最后,在服務器因某些原因結束運行以后,調用 Tracker::save_dfile() 保存下載信息。這樣,一旦服務器再次投入運行,可以恢復當前的狀態。
其它信息:
1、 BT源碼的分布:
把BT的源碼展開之后,可以看到有一些python程序,還有一些說明文件等等,此外還有一個BitTorrent目錄。這些 python程序,實際是一些小工具,比如制作 metafile的btmakemetafile.py、運行tracker服務器的bttrack.py、運行BT client端的 btdownloadheadless.py 等等。而這些程序中,用到的一些 python 類的實現,都放在子目錄 BitTorrent 下面。我們的分析工作,通常是從工具程序入手,比如 bttrack.py,而隨着分析的展開,則重點是看 BitTorrenet子目錄下的代碼。
BT作者 Bram Cohen 在談到如何開發可維護的代碼的一篇文章中(http://www.advogato.org/article/258.html),其中提到的一條就是開發一些小工具以簡化工作,我想BT的這種源碼結構,也正是作者思想的一種體現吧。
2、 我們看到,python和我們以前接觸的 c/c++ 不一樣的第一個地方就是它的函數在定義的時候,不用指定參數類型。既然這樣,那么,在調用函數的時候,你可以傳遞任意類型的參數進來。例如這樣的函數:
- def foo(arg):
- print type(arg)
-
- 你可以這樣來調用:
- a = 100
- b = “hello world”
- foo(a)
- foo(b)
-
- 輸出結果是:
- <type ‘int’>;
- <type ‘str’>;
-
這是因為,第一次調用 foo()的時候,傳遞的是一個整數類型,而第二次調用的時候,傳遞的是一個字符串類型。
這種參數具有動態類型的特性,是 c/c++等傳統的語言是所不具備的。這也是 python 被稱為動態語言的一個原因吧。C++的高級特性模板,雖然也使得參數類型可以動態化,但使用起來,遠沒有python這么簡單方便。
Tracker 服務器源碼分析之三:HTTPHandler 類
本篇文章分析 HTTPHandler類,它在 HTTPHandler.py 文件中。
上一篇我們講到, RawServer 只負責網絡 I/O,也就是從網絡上讀取和發送數據,至於讀到的數據如何分析,以及應該發送什么樣的數據,則交給 Handler 類來處理。如果是用 c++ 來實現的話,那么 Handler 應該是一個接口類(提供幾個虛函數作為接口),但是 python 動態語言的特性,並不需要專門定義這么一個接口類,所以實際上並沒有 Handler 這么一個類。任何一個提供了以下成員函數的類,都可以作為一個 Handler 類來與 RawServer 配合,它們是:
external_connection_made():在建立新的連接的時候被調用
data_came_in():連接上有數據可讀的時候被調用
connection_flushed():當在某個連接上發送完數據之后被調用
HTTPHandler 就是這樣一個 Handler 類,它具備以上接口。
HTTPHandler 代碼很少,因為它把主要工作又交給 HTTPConnection 了。
我們看 HTTPHandler 類的這幾個函數:
l external_connection_made():
每當新來一個連接的時候,就創建一個 HTTPConnection 類。
l data_came_in():
當連接上有數據可讀的時候,調用 HTTPConnection::data_came_in()。我們接下去看HTTPConnection::data_came_in()。
我們知道,BT client端與 tracker服務器之間是通過tracke HTTP 協議來進行通信的。HTTP協議分為請求(request)和響應(response),具體的協議請看相關的 RFC 文檔。我這里簡單講一下。
對 tracke 服務器來說,它讀到的數據是 client 端的HTTP 請求。
HTTP請求以行為單位,行的結束符是“回車換行”,也就是 ascii 字符 “\r”和“\n”。
第一行是請求的 URL,例如:
GET /announce?ip=aaaaa;port=bbbbbbb HTTP/1.0
這行數據被空格分為三部分,
第一部分GET表示命令,其它命令還有POST、HEAD等等,常用的就是GET了。
第二部分是請求的URL,這里是 /announce?ip=aaaaa;port=bbbbbbb。如果是普通的上網瀏覽網頁,那么URL 就是我們要看的網頁在該web服務器上的相對路徑。但是,這里的URL僅僅是交互信息的一種方式,client 端把要報告給 tracker 的信息,放在URL中,例子里面是 ip 和 port,更詳細的信息請看“BT協議規范”中 tracker 協議部分。
第三部分是HTTP協議的版本號,在程序中忽略。
接下來的每一行,都是HTTP協議的消息頭部分,例如:
Host:www.sina.com.cn
Accept-encoding:gzip
通過消息頭,tracker服務器可以知道 client端的一些信息,這其中比較重要的就是 Accept-encoding,如果是 gzip ,那么說明 client 可以對 gzip 格式的數據進行解壓,那么tracker服務器就可以考慮用 gzip 把響應數據壓縮之后再傳回去,以減少網絡流量。我們可以在代碼中看到相應的處理。
在消息頭的最后,是一個空行,表示消息頭結束了。對GET和HEAD命令來說,消息頭的結束,也就意味着整個client端的請求結束了。而對 POST 命令來說,可能后面還跟着其它數據。由於我們的 tracker服務器只接受 GET 和 HEAD 命令,所以在協議處理過程中,如果遇到空行,那么就表示處理結束。
HTTPConnection::data_came_in() 用一個循環來進行協議分析:
首先是尋找行結束符號:
i = self.buf.index('\n')
(我認為僅僅找 “\n”並不嚴謹,應該找 “\r\n”這個序列)。
如果沒有找到,那么 index() 函數會拋出一個異常,而異常的處理是返回 True,表示數據不夠,需要繼續讀數據。
如果找到了,那么 i 之前的字符串就是完整的一行。於是調用協議處理函數,代碼是:
self.next_func = self.next_func(val)
在 HTTPConnection 的初始化的時候,有這么一行代碼:
self.next_func = self.read_type
next_func 是用來保存協議處理函數的,所以,第一個被調用的協議處理函數就是 read_type()。它用來分析client端請求的第一行。在 read_type() 的最后,我們看到:
return self.read_header
這樣,在下一次調用 next_func 的時候,就是調用 read_header()了,也就是對 HTTP 協議的消息頭進行分析。
下面先看 read_type(),
它首先把 GET 命令中的 URL 部分保存到 self.path中,因為這是 client端最關鍵的信息,后面要用到。
然后檢查一下是否是GET或者HEAD命令,如果不是,那么說明數據有錯誤。返回None,否則return self.read_header
接下來我們看read_header(),
這其中,最重要的就是對空行的處理,因為前面說了,空行表示協議分析結束。
在檢查完 client 端是否支持 gzip 編碼之后,調用:
r = self.handler.getfunc(self, self.path, self.headers)
通過一層層往后追查,發現 getfunc() 實際是 Tracker::get(),也就是說,真正對 client 端發來的請求進行分析,以及決定如何響應,是由 Tracker 來決定的。是的,這個 Tracker 在我們tracker 服務器源碼分析系列的第一篇文章中就已經看到了。在創建 RawServer 之后,馬上就創建了一個 Tracker 對象。所以,要了解 tracker 服務器到底是如何工作的,需要我們深入進去分析 Tracker 類,那就是我們下一篇文章的工作了。
在調用完 Tracker::get() 之后,返回的是決定響應給 client 端的數據,
if r is not None:
self.answer(r)
最后,調用 answer() 來把這些數據發送給 client 端。
對 answer() 的分析,我們在下一篇分析 Tracker類的文章中一並講解。
l connection_flushed():
tracker服務器用的是非阻塞的網絡 I/O ,所以不能保證在一次發送數據的操作中,把要發送的數據全部發送出去。
這個函數,檢查在某個連接上需要發送的數據,是否已經全部被發送出去了,如果是的話,那么關閉這個連接的發送端。(為什么僅僅關閉發送端,而不是完全關閉這個連接了?疑惑)。
本篇文章分析 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 value 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 (RFC191
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 value associated with key as eight random hex values, and the tracker accepts any string from clients.
compact=1 - when a client sends this, the 'peers' return value 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。
摘自:http://bbs.chinaunix.net/thread-556756-1-1.html
