[译]BitTorrent协议规范
BitTorrent 是一個用于文件分發(fā)的協(xié)議。它通過 URL 來標識內(nèi)容,其設計使其可以與 Web 無縫集成。BitTorrent 相對于一般 HTTP 的優(yōu)勢在于,當相同文件的多個下載并行進行時,下載者之間還可以互傳數(shù)據(jù),這就使得文件源在僅增加少量負載的情況下支持數(shù)量眾多的下載成為可能。
BitTorrent 文件分發(fā)由這些實體組成
- 一個普通的 Web 服務器
- 一個靜態(tài)的 ‘metainfo’ 文件
- 一個 BitTorrent tracker
- 一個 ‘原始的’ 下載者
- 終端用戶 Web 瀏覽器
- 終端用戶下載者
理想情況下,單個文件有多個終端用戶在下載。
要提供 BitTorrent 下載服務,主機需要執(zhí)行如下步驟
注:
總結一下,提供 BitTorrent 文件下載服務,需要如下步驟:
1. 啟動 Tracker 服務。
2. .torrent 文件的生成和發(fā)布:這包括根據(jù)要發(fā)布的文件本身的內(nèi)容和 Tracker 服務器的 URL 生成 metainfo 文件,即 .torrent 文件;將 .torrent 文件發(fā)布到 Web 服務器上,提供對該 .torrent 文件的下載服務;將 .torrent 文件的鏈接盡可能的傳播出去。
3. 啟動提供文件的下載源。
真正用于實際的文件下載的組件是 Tracker 服務器,和文件的下載源節(jié)點。
要啟動下載,用戶需執(zhí)行如下步驟
注:
總結一下,BitTorrent 文件下載有兩個過程:一是下載 .torrent 文件;二是使用 BitTorrent 客戶端下載文件,數(shù)據(jù)的來源將是網(wǎng)絡中包含文件數(shù)據(jù)的其它節(jié)點。
B編碼
字符串表示為,十進制的長度前綴,后跟冒號和字符串本身。比 4:spam 表示 spam。
整數(shù)表示為,i 后跟十進制數(shù)字,然后是一個 e 字符表示結束。比如 i3e 表示 3,i-3e 表示 -3。整數(shù)沒有大小限制。i-0e 是無效的。所有以 0 為前導的編碼,比如 i03e,都是無效的,除了 i0e,而它當然表示 0。
列表被編碼為,l 后跟它們的元素(也是 B 編碼的),然后是 e 字符表示結束。比如 l4:spam4:eggse 表示 ['spam', 'eggs']。
字典編碼為,d 后交替地跟著鍵和它們的對應值,然后是 e 字符表示結束。比如 d3:cow3:moo4:spam4:eggse 表示 {'cow': 'moo', 'spam': 'eggs'},而 d4:spaml1:a1:bee 表示 {'spam': ['a', 'b']}。鍵必須是字符串,且順序排列(以原始串排序,而不是字母順序)。
注:
BitTorrent 協(xié)議發(fā)明了一種編碼方式,即 B 編碼。B 編碼編碼 4 種數(shù)據(jù)結構或對象,即字符串,整數(shù),列表,和字典(即映射)。基本數(shù)據(jù)類型是字符串和數(shù)字,列表和字典則是兩種最常用的容器。
metainfo 文件
Metainfo 文件(即 .torrent 文件)是 B 編碼的字典,它具有如下的鍵:
announce
- Tracker 的 URL。
info
- 它映射到一個字典,下面會描述它的鍵。
一個 .torrent 文件中所有包含文本的字符串必須以 UTF-8 編碼。
info 字典
name 鍵映射為一個 UTF-8 編碼的字符串,它建議的保存文件(或字典)時所采用的名字。這純粹是建議性的。
piece length 映射為文件分割后每個片的字節(jié)數(shù)。為了便于數(shù)據(jù)傳輸,文件被分割為固定大小的片,它們具有相同的長度,除了最后的文件片可能由于截斷而不同外。piece length 幾乎總是 2 的冪,最常見的是 2^18 = 256K(3.2 版之前的 BitTorrent 版本使用 2^20 = 1M 作為默認值)。
pieces 映射為長度是 20 的整數(shù)倍的字符串。它將被細分為長度為 20 的字符串,其中每個都是對應 index 處的片的 SHA1 hash 值。
還有一個鍵 length 或鍵 files,但不會都有或都沒有。如果是 length,表示下載是單個文件,否則表示下載是目錄結構中的一系列文件。
在單個文件的情況中,length 映射為文件的字節(jié)長度。
出于其他鍵的目的,多文件的情況僅被看作將 files 列表中出現(xiàn)的文件順序連接形成的單個文件。文件列表是 files 映射的值,它是包含如下鍵的字典的列表:
length - 文件的長度,以字節(jié)為單位。
path - UTF-8 編碼的字符串的列表,對應于子目錄的名字,它的最后部分是實際的文件名(長度為 0 的列表是錯誤情況)。
在單個文件的情況中,name 鍵是文件名,在多文件的情況中,它是目錄名。
注:
用 JSON 來表示 .torrent 文件使我們可以對這種文件格式的結構有更清晰的認識。單個文件的 .torrent 文件結構如下:
多個文件的 .torrent 文件結構如下:
{"announce":"xxxx","info":{"name":"xxxx","piece length":"xxxx","pieces":"xxxx","files":[{"length":"xxxx","path":"xxxx"},{"length":"xxxx","path":"xxxx"},{"length":"xxxx","path":"xxxx"}]} }trackers
Tracker GET 請求具有如下的鍵:
info_hash
- 是 metainfo 文件中 info 值的 B 編碼形式的 20 字節(jié) SHA1 哈希。這個值幾乎肯定必須被轉義。
- 注意,這是 metainfo 文件的一個子串。info-hash 必須是 .torrent 文件中找到的編碼形式的哈希,這與對 metainfo 文件進行 B 解碼,提取 info 字典并對其進行編碼相同,當且僅當 B 解碼器完全驗證輸入之后(比如鍵的順序,沒有前導 0)。相反,這意味著客戶端必須拒絕無效的 metainfo 文件或直接提取子串。它們不得對無效數(shù)據(jù)執(zhí)行解碼-編碼循環(huán)。
peer_id
- 一個下載器用作其 ID 的長度為 20 的字符串。每個下載器在新下載開始時隨機生成它自己的 ID。這個值也幾乎肯定必須被轉義。
ip
- 一個可選的參數(shù),它給出了這個 Peer 的 IP 地址(或域名)。如果它與 tracker 位于相同機器的話,通常被用作源。
port
- 當前 Peer 監(jiān)聽的端口號。下載器的常見行為是,嘗試監(jiān)聽端口 6881,但如果那個端口被占用則嘗試端口 6882,然后是 6883,等等,直到 6889 之后放棄。
uploaded
- 到目前為止上傳的數(shù)據(jù)量,以十進制 ascii 編碼。
downloaded
- 到目前為止下載的數(shù)據(jù)量,以十進制 ascii 編碼。
left
- 當前 Peer 仍然需要下載的字節(jié)數(shù),以十進制 ascii 編碼。注意,這個值不能用文件的長度和已經(jīng)下載的數(shù)據(jù)量來計算,因為它可能是恢復的下載,而且,也有可能下載回來的一些數(shù)據(jù)在完整性檢查時失敗,而不得不重新下載。
event
- 這是一個可選的鍵,它映射為 started,completed,或 stopped(或empty,與沒有這個鍵一樣)。如果不存在,則這是定期進行的公告請求之一。使用 started 的公告在下載首次啟動時發(fā)送,使用 completed 的將在下載完成時發(fā)送。如果啟動時文件已經(jīng)完成則不會發(fā)送 completed。當停止下載時下載器發(fā)送一個使用 stopped 的公告。
注:看了半天也沒看懂,到底如何計算 info_hash。ip 字段的用作源到底是什么意思?是用作數(shù)據(jù)源么?
由此可見,BitTorrent 客戶端向 Tracker 服務器發(fā)送的請求數(shù)據(jù),如果以 JSON 格式表示的話,看起來可能像下面這樣:
但具體是什么樣的結構化數(shù)據(jù)表示格式,目前還看不出來。
Tracker 的響應也是 B 編碼的字典。如果 Tracker 的響應包含 failure reason 鍵,則它是解釋查詢失敗的原因的可讀字符串,且不會再有其它鍵。否則,它必須包含兩個鍵:interval,它是下載器在兩次正常的重新請求之間應該等待的秒數(shù),和 peers。peers 是字典的列表,即網(wǎng)絡中的 Peer 信息,列表中的每一項都包含鍵 peer id,ip 和 port,它們分別是 Peer 為自己選擇的 ID,字符串形式的 IP 地址或域名,和端口號。注意,如果事件發(fā)生或它們需要更多 Peers,則下載器可能在非調(diào)度時間進行重請求。
更常見的是,Trackers 返回 Peer 列表的兼容表示,參見 BEP 23。
注:
Tracker 服務器的響應數(shù)據(jù)的格式,說明的還是比較清晰的,即 B 編碼的字典。響應數(shù)據(jù)的格式分為兩種,分別是請求失敗時的響應,和請求成功時的響應。請求失敗時的響應格式,如果用 JSON 格式來表示的話,大概看起來像這樣:
請求成功時,響應中包含了正在下載相同資源的其它節(jié)點的信息,如果用 JSON 格式來表示這種響應的話,大概看起來像這樣:
{"interval":12345,"peers":[{"peer id":"xxxxxx","ip":"xxxxxx","port":1234},{"peer id":"xxxxxx","ip":"xxxxxx","port":1234},{"peer id":"xxxxxx","ip":"xxxxxx","port":1234}] }如果你想對 metainfo 文件或 Tracker 查詢做擴展,請與 Bram Cohen 合作來確認所有擴展的兼容性。
通過 UDP Tracker 協(xié)議通告也很常見。
注:
由上面的內(nèi)容不難理解,Tracker 服務器 BitTorrent 網(wǎng)絡中的中心化服務器,主要用于管理網(wǎng)絡中的節(jié)點。
Peer 協(xié)議
BitTorrent 的 Peer 協(xié)議操作基于 TCP 或 uTP。
Peer 連接是對稱的。消息在兩個方向上的發(fā)送看起來是一樣的,數(shù)據(jù)可在任一方向上流動。
Peer 協(xié)議通過 metainfo 文件中所描述的索引引用文件的片段,索引從零開始。當 Peer 完成一個片的下載,并檢查了哈希值的匹配性,它將向它所有的 Peers 通告它具有該片。
連接在任一端包含兩個狀態(tài)位:阻塞或不阻塞,以及是否感興趣。(注:即與對端的數(shù)據(jù)通道是否暢通,以及對端是否具有自己需要的數(shù)據(jù)。)阻塞是一種通知,在阻塞解除之前將不會發(fā)送任何數(shù)據(jù)。阻塞背后的原因和常見技術將在本文后面解釋。
數(shù)據(jù)傳輸發(fā)生在一端需要數(shù)據(jù)而另一端沒有阻塞的時候。感興趣狀態(tài)必須隨時保持更新 - 當下載者無需在對端處于非阻塞狀態(tài)的話,就向對端請求數(shù)據(jù)的時候,它們必須表達缺乏興趣,盡管正在被阻塞。正確實現(xiàn)這一點很棘手,但是下載者可以知道哪些 Peers 會在解除阻塞時立即開始下載。
連接開始時處于阻塞且不感興趣狀態(tài)。
當傳輸數(shù)據(jù)時,下載者應該一次在隊列中保持多個片的請求以獲得較好的 TCP 性能(這被稱為 ‘pipelining’)。另一方面,無法立即寫入 TCP 緩沖區(qū)的請求應該放進內(nèi)存隊列中,而不是應用級的網(wǎng)絡緩沖區(qū)中,以使它們在阻塞發(fā)生時可以被扔掉。(注:內(nèi)存緩沖區(qū)和應用級緩沖區(qū)到底分別指什么?)
Peer 線協(xié)議由握手消息及其后永不停止的長度前綴消息流組成。握手消息由字符 9 (十進制)及其后面的字符串 BitTorrent protocol 構成。前導字符是長度前綴,希望其他新協(xié)議可以做同樣的事情,因此可以在很小的程度上相互區(qū)分。
協(xié)議中后續(xù)發(fā)送的整數(shù)都以四字節(jié)大尾端編碼。
固定頭部之后是 8 個保留字節(jié),在當前所有的實現(xiàn)中它們都是 0。如果你想使用這些字節(jié)擴展協(xié)議,請與 Bram Cohen 合作,以確保所有擴展可以兼容地完成。
接下來是 metainfo 文件中 info 值的 B 編碼形式的 20 字節(jié) SHA1 哈希。(這個值與向 Tracker 服務器公告的 info_hash 相同,只是在這里它是原始的而不是在這里引用的)。如果兩端沒有發(fā)送相同的值,則切斷連接。一個可能的例外是,如果下載者想要通過單個端口執(zhí)行多個下載,則他們可能會等待傳入連接首先提供下載哈希,并且如果它在列表中的話則以相同的哈希響應。
下載哈希后面的是 20 字節(jié)的 Peer ID,該 Peer ID 由 Tracker 請求報告,并包含在 Tracker 響應的 Peer 列表中。如果接收端的 Peer ID 與發(fā)起端的期待不匹配,則它切斷連接。
這就是握手消息,接下來是長度前綴和消息的交替流。長度為 0 的消息是 keepalives,它們會被忽略。Keepalives 通常每 2 分鐘發(fā)送一次,但是注意,在預期數(shù)據(jù)時可以更快地完成超時。
Peer 消息
所有的非 keepalive 消息以一個標識消息類型的字節(jié)開始。
可能的值為:
- 0 - choke
- 1 - unchoke
- 2 - interested
- 3 - not interested
- 4 - have
- 5 - bitfield
- 6 - request
- 7 - piece
- 8 - cancel
choke,unchoke,interested,和 not interested 沒有載荷。
‘bitfield’ 只作為第一條消息發(fā)送。它的載荷是一個位域,其中下載器已經(jīng)發(fā)送的每個索引設置為 1,其它設置為 0。沒有任何東西的下載器可以跳過 bitfield 消息。位域的第一個字節(jié)從高位到低位分別對應于索引 0 - 7。接下來的一個是 8 - 15,等等。最后一個字節(jié)中多余的位設置為 0。
have 消息的載荷是一個數(shù)字,是下載器剛剛完成并檢查了哈希值的索引。
request 消息包含索引,起始位置,和長度。后兩個是字節(jié)偏移量。長度通常是 2 的冪,除非它被文件尾截斷。所有當前的實現(xiàn)使用 2^14 (16 kiB),當請求比這個值更大數(shù)量的數(shù)據(jù)時,連接關閉。
cancel 消息具有與 request 消息相同的載荷。它們通常僅在下載結束時發(fā)送,稱為“結束游戲模式”。當下載快結束時,最后一些數(shù)據(jù)片傾向于從單個調(diào)制解調(diào)器線上下載,這需要很長時間。為了確保最后的數(shù)據(jù)片快速地到達,一旦特定下載器還沒有下載到的所有數(shù)據(jù)片的請求都處于掛起狀態(tài),則它向當前正從其下載的每個 Peer 請求所有的數(shù)據(jù)。為了防止這種情況變得特別低效,每次數(shù)據(jù)片到達時它都向其它 Peer 發(fā)送 cancel。
piece 消息包含索引,起始位置,和數(shù)據(jù)片。注意,它們隱式地與請求消息相關聯(lián)。如果 choke 和 unchoke 消息快速連續(xù)地發(fā)送和/或傳輸速度非常慢的話,可能會有未預期的數(shù)據(jù)片到達。
下載器通常以隨機順序下載數(shù)據(jù)片,這樣可以很好地防止它們擁有任何 Peers 的片的嚴格子集或超集。
Choking 有幾個原因。當一次性通過多個連接發(fā)送時,TCP 擁塞控制的表現(xiàn)很差。而且,choking 讓每個 Peer 使用 tit-for-tat-ish 算法以確保它們獲得一致的下載速率。
下面描述的 choking 算法正是當前部署的。所有新算法在完全由他們自己組成的網(wǎng)絡中,以及在主要由這個算法組成的網(wǎng)絡中,都能很好地工作是非常重要的。
一個好的 choking 算法應該滿足一些標準。它應該限制并發(fā)上傳的數(shù)量以獲得良好的 TCP 性能。它應該避免快速地 choking 和 unchoking,這被稱為 抖動。它應該回應讓它下載的 Peer。最后,它應該偶爾嘗試使用未使用的連接,以確定它們是否可能比當前使用的更好,這稱為樂觀的 unchoking。
當前部署的 choking 算法通過僅改變每隔 10 秒鐘 choked 一次的 Peer 避免了抖動。它通過解除對其具有最佳下載速率并且感興趣的四個 peers 來實現(xiàn)往復和上傳數(shù)量的限制。擁有更高上傳率但不感興趣的 peer 會被 unchoked,如果它們變得被感興趣則最糟糕的上傳者被 choked。如果下載者擁有一個完整的文件,它使用它的上傳速率而不是它的下載速率來決定誰 unchoke。
對于樂觀的 unchoking,在任何時間都有一個單獨的 peer,它處于 unchoked 狀態(tài)而無論它的上傳速率是多少(如果感興趣,它被作為四個允許的下載者中的一個)。哪個 peer 是樂觀的 unchoked 每隔 30 秒輪轉一次。為了給他們一個很好的機會獲得一個完整的片段上傳,新的連接的開始時間是當前樂觀的unchoke的三倍,與旋轉中的其他任何地方一樣。
資源
- BitTorrent 經(jīng)濟論文 列出了一些客戶端應該實現(xiàn)的用于優(yōu)化性能的請求和 choking 算法。
- 當開發(fā)新的實現(xiàn)時,Wireshark 協(xié)議分析器和它的 針對 bittorrent 的解剖器 在調(diào)試和對比已有的實現(xiàn)時可能很有用。
版權
本文檔已置于公共領域。
原文鏈接。
總結
以上是生活随笔為你收集整理的[译]BitTorrent协议规范的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mupdf-android-viewer
- 下一篇: LEGO EV3 通信开发者套件