BitTorrent 是一種用於分發文件的協議。它通過 URL 標識內容,並且旨在與 Web 網絡無縫集成。相對於簡單的 HTTP 下載,它的優勢在於當多個用戶同時下載同一個文件時,這些用戶之間可以互相上傳文件塊,多個用戶共同參與文件傳輸,而非依賴於單一的文件伺服器。使得文件源能夠支持大量用戶同時下載而只對其自身的負載產生較小的影響。
BitTorrent 文件分發由以下實體組成:#
- 一個普通的 Web 伺服器
- 一個靜態的 metainfo 文件
- 一個 BitTorrent tracker
- 一個原始下載器
- 網絡瀏覽器
- BT 下載客戶端
- 一個文件在最理想情況下能夠被許多最終用戶共享下載。
要開始服務,主機要執行以下步驟:#
- 啟動一個 tracker(或者已經運行一個)。
- 啟動一個普通的 Web 伺服器,比如 Apache(或者已經有一個運行中)。
- 在他們的 Web 伺服器上將擴展名 .torrent 與 MIME 類型 application/x-bittorrent 相關聯(或者已經這樣做了)。
- 使用要提供服務的完整文件和 tracker 的 URL 生成 metainfo (.torrent) 文件。
- 將 metainfo 文件放在 Web 伺服器上。
- 從其他網頁鏈接到 metainfo (.torrent) 文件。
- 啟動一個已經擁有完整源文件的下載器。
要開始下載,用戶執行以下操作:#
- 安裝 BitTorrent 客戶端。
- 上網衝浪。
- 打開 .torrent 文件的鏈接。
- 選擇在本地保存文件的位置,或選擇要繼續的部分下載。
- 等待下載完成。
- 退出下載客戶端(它會一直上傳直到收到退出指令)。
Bencode 編碼#
Bencode 編碼用於將任意數據類型的值序列化(也稱為 “串行化”)為字節流,以便在網絡上傳輸或存儲在磁碟上。 Bencode 編碼支持字符串、整數、列表和字典四種基本數據類型。
- 對於字符串類型,採用長度前綴加冒號的方式表示,例如”spam” 會被編碼為 4 。
- 對於整數類型,以字符”i” 開始,後面跟著十進制表示的整數和字符”e”,例如 3 被編碼為 i3e,-3 被編碼為 i-3e 。
- 對於列表類型,以字符”l” 開始,後面跟著列表中各個元素的 Bencode 編碼,最後以字符”e” 結束,例如 [‘spam’, ‘eggs’] 被編碼為 l4:spam4 。
- 對於字典類型,以字符”d” 開始,後面跟著按照 key 的 ASCII 碼排序的鍵值對的 B 編碼,最後以字符”e” 結束,例如 {‘cow’: ‘moo’, ‘spam’: ‘eggs’} 被編碼為 d3:cow3:moo4:spam4 。需要注意的是,所有字典類型的鍵必須是字符串,並且按照原始字符串的排序方式進行排序。
Bencode 編碼提供了一種簡單、緊湊、可讀性較強的數據序列化方案,適用於各種數據類型的序列化和反序列化操作。
Metainfo 文件#
MetaInfo 文件(也稱為 .torrent 文件)是使用 bencoding 編碼的字典,包含以下鍵:
announce
Tracker 的 URL 地址。
info
這個鍵映射到一個字典中,其中包含下面描述的鍵。
在包含文本的 .torrent 文件中的所有字符串都必須使用 UTF-8 編碼。
info dictionary#
BitTorrent 下載文件時所使用的元數據信息。它包含了多個鍵值對,其中每個鍵都代表了一個特定的屬性,例如建議保存的文件名、文件塊的大小和哈希值等。這些屬性可以幫助客戶端正確地下載並組裝文件。
其中一些重要的屬性包括:
- name:建議保存的文件名,它純粹是為了提供建議而不具有強制性。
- piece length:文件被分成固定大小的塊,該屬性指定每個塊的大小(通常是 2 的幂次方)。
- pieces:每個塊對應的哈希值,用於驗證文件的完整性。
- length:文件長度(只適用於單個文件的情況),或者所有文件拼接後的總長度。
如果下載的是單個文件,則只需要使用 name 和 length 屬性。如果是多個文件,則需要使用 files 列表來表示文件的目錄結構,其中每個文件都由一個字典表示,包含 length 和 path 屬性。
trackers#
Tracker GET 請求包含以下關鍵字:
info_hash:元信息文件中 info 值的 bencoded 形式的 20 字節 SHA1 哈希值。該值幾乎肯定需要進行轉義。請注意,這是元信息文件的子字符串。 info-hash 必須是 .torrent 文件中編碼形式的哈希值,這與對元信息文件進行 bencoded 解碼、提取 info 字典並編碼它完全相同,如果 bencoded 編碼完全驗證了輸入(例如,鍵排序、前導零的缺失),則必須這樣做。反之,這意味著客戶端必須拒絕無效的元信息文件或直接提取子字符串。不能對無效數據執行解碼 - 編碼往返。
peer_id:長度為 20 的字符串,用作此下載器的 ID 。每個下載器在開始新下載時都會隨機生成自己的 ID 。該值也幾乎肯定需要進行轉義。
ip:可選參數,指定此對等方所在的 IP 地址(或 DNS 名稱)。通常用於源地址,如果源位於與跟踪器相同的計算機上,則使用此參數。
port:此對等方正在偵聽的端口號。常見行為是讓下載器嘗試偵聽端口 6881,並在該端口被佔用時嘗試 6882 、 6883 等端口號,並在 6889 後放棄。
uploaded:到目前為止已上傳的總量,以十進制 ASCII 編碼。
downloaded:到目前為止已下載的總量,以十進制 ASCII 編碼。
left:此對等方仍需下載的字節數,以十進制 ASCII 編碼。請注意,這不能從 downloaded 和文件長度計算出來,因為它可能是恢復下載,並且有可能一些已下載的數據未通過完整性檢查而需要重新下載。
event:這是一個可選關鍵字,映射到 started 、 completed 或 stopped(或空,與不存在相同)。如果不存在,則是定期通告之一。當下載開始時,使用 started 發送一個通知;當下載完成時,使用 completed 發送一個通知。如果在開始時文件已完成,則不發送 completed 。下載器停止下載時會使用 stopped 發送一個通告。
Tracker 響應是 bencoded 字典。如果跟踪器響應具有 failure reason 關鍵字,則該關鍵字映射到人類可讀的字符串,用於解釋查詢失敗的原因,並且不需要其他關鍵字。否則,它必須具有兩個關鍵字:interval,其映射到下載器應在定期重新請求之間等待的秒數;並且 peers,其映射到對等方列表的字典,每個字典包含 peer id 、 ip 和 port 這三個關鍵字,分別表示對等方的自選 ID 、 IP 地址或 DNS 名稱以及端口號。請注意,如果發生事件或需要更多對等方,下載器可能在非定期時間上重新請求。
更常見的是,跟踪器會返回對等方列表的壓縮表示形式,請參見 Tracker Returns Compact Peer Lists 。
如果您想對元信息文件或跟踪器查詢進行任何擴展,請與 Bram Cohen 協調,以確保所有擴展都是兼容的。
通常還可以通過 UDP 跟踪器協議進行通告。
peer protocol#
BitTorrent 的對等協議通過 TCP 或 uTorrent 傳輸協議運行。在 BitTorrent 中,下載者和上傳者都是對等的,數據可以在它們之間相互流動。數據被分成許多小塊,從多個源同時下載,從而提高下載速度和傳輸的可靠性。每當下載者完成一塊文件的下載,並檢查哈希值匹配時,它會向所有對等者廣播該塊的信息。
連接狀態有兩類:阻塞或未阻塞;感興趣或不感興趣。阻塞表示沒有數據將被發送,直到解除阻塞。數據傳輸只有在一方感興趣且另一方未阻塞的情況下才會發生。為了實現正確的數據傳輸,下載者必須保持感興趣的狀態並及時更新,儘管受到了阻塞。傳輸數據時,下載者應保持多個塊的請求排隊以獲得良好的 TCP 性能,而請求不能立即寫入 TCP 緩衝區的請求則應排隊在內存中,以便在阻塞發生時全部丟棄。
對等協議由握手過程和永無止境的長度前綴消息組成。握手以十九(十進制)字符開頭,後面跟著字符串 “BitTorrent 協議” 。在連接兩個節點之前,它們需要進行握手以交換必要的信息。握手開始時,發送方會向接收方發送一個固定的握手消息,其中包括協議版本、保留字節、元數據哈希值和節點 ID 。接收方會驗證這些信息,並檢查它們是否與期望的值匹配。如果有任何不匹配,連接將斷開。握手成功後,雙方開始交換數據。數據是通過一系列長度前綴和消息來傳輸的。長度前綴表示該消息的長度,然後是實際的消息內容。
peer messages#
所有非激活消息都以單個字節開頭,下面給出了它們的類型。
可能的值為:
- 0 – choke
- 1 – unchoke
- 2 – interested
- 3 – not interested
- 4 – have
- 5 – bitfield
- 6 – request
- 7 – piece
- 8 – cancel
‘choke’, ‘unchoke’, ‘interested’, and ‘not interested’ 沒有有效載荷。
bitfield 消息:該消息只會作為第一個消息被發送。它的負載是一個位域,每個下載器發送的索引都被設置為 1,其餘索引則被設置為 0 。沒有任何數據的下載器可以跳過這個 “bitfield” 消息。位域的第一個字節對應於索引 0-7(從高位到低位),接下來是 8-15,以此類推。結尾處的多餘位被設置為 0 。
have 消息:該消息的負載是一個單一的數字,表示該下載器完成並檢查哈希值的索引。
request 消息:該消息包含一個索引、偏移量和長度。最後一個參數通常是 2 的幂次方,除非已經到達文件的末尾。所有當前的實現都使用 2^14(16 kiB),並關閉請求超出此限制的連接。
cancel 消息:與請求消息具有相同的有效負載。它們通常只在下載結束時發送,在所謂的 “終局模式” 期間。當下載幾乎完成時,最後幾部分傾向於從單個軟管調製解調器線路下載,這需要很長時間。為了確保最後幾塊快速進入,一旦給定下載器尚未收到的所有部分的請求當前處於待處理狀態,它就會向正在下載的每個人發送所有內容的請求。為了防止這變得非常低效,每次有一件作品到達時,它都會向其他人發送取消。
piece 消息:包含索引、開始和片段。請注意,它們與請求消息隱式關聯。如果快速連續發送阻塞和取消阻塞消息和 / 或傳輸速度非常慢,則可能會出現意外的片段。
首先,下載器通常會隨機下載文件的不同部分,從而避免出現某個下載器擁有其他下載器的子集或超集的情況。其次,為了保證每個下載器都能獲得一致的下載速度,需要進行限制上傳速度的操作(也稱作 choking)。這種算法能夠讓下載器之間使用類似於” 以牙還牙” 的策略,以保證自己能獲得穩定的下載速度。最後,當前已經在使用的 choking 算法,並強調了新算法需要在不同網絡環境下都表現良好的重要性。
首先,為了保證良好的 TCP 性能,算法需要限制同時上傳的連接數。其次,為了避免頻繁地 choke 和 unchoke,算法需要穩定地選擇上傳對象。此外,在一定程度上應該回報那些讓自己下載的 peer 。最後,算法應該嘗試利用未使用的連接來提高下載速度,這被稱作 optimistic unchoking 。
當前部署的 choking 算法採取了一些措施來實現這些目標。為了避免頻繁的變化,算法每 10 秒才會更改哪些 peer 被 choke 。為了回報其他人,並限制上傳連接數量,算法選擇前四個下載速率最好且有興趣的 peer 來 unchoke 。如果有其他 peer 的上傳速率更好但是沒有興趣,則該 peer 也可能會被 unchoke,如果它突然有了興趣,就會 choke 掉下載速率最差的 peer 。如果某個 downloader 已經完整地下載了文件,則它將使用上傳速率而不是下載速率來決定 unchoke 哪些 peer 。
至於 optimistic unchoking,算法會每 30 秒輪流選擇一個 peer 進行 unchoke,無論其上傳速率如何(只要有興趣就算一個下載器)。在 rotation 中,新連接被三倍概率放在當前 optimistic unchoke 的位置上,以便給它們足夠的機會上傳完整的數據塊。