1. 程式人生 > >P2P-BT對端管理協議(附BT協議1.0)

P2P-BT對端管理協議(附BT協議1.0)

對端管理

指的是遠端peer集合的管理(雖然自身client也可以視為一個peer,但對端管理不包括自身peer)

一個客戶端(client)必須維持與每一個遠端peer連線的狀態資訊,即1V1關係(本端對某個遠端peer)
在本程式碼中PcPeer指這種1V1關係,而不是僅指遠端peer
對於每個連線連線來說,每一端的peer應該是4種狀態之一:一端是interested或者not interested,則另一端是choking或者unchoking。反之亦然。Choking表示直到unchoking發生之前,都不會有任何資料傳送給連線另一端的peer
程式碼如下:

#define
CPE_STAT_NONE ((char)0x00)
#define CPE_STAT_LC_I ((char)0x01) //B 是否擁有 A 感興趣的分片 #define CPE_STAT_RM_I ((char)0x02) //A 是否擁有 B 感興趣的分片 #define CPE_STAT_LC_C ((char)0x04) //A 能否向 B 請求分片資料 #define
CPE_STAT_RM_C ((char)0x08) //B 能否向 A 請求分片資料

這些資訊由如下關鍵字來描述:

  1. choke(拒絕/阻塞)、unchoke(接受)兩種狀態
    如果遠端peer對本client是choke,遠端peer將不會接收來自本client的請求
    而本client在接收到來自遠端peer的choke訊息後,就不會再試圖向遠端peer傳送資料請求,因為本客戶端會認為所有發給遠端peer的請求都已被丟棄

  2. interest(興趣)
    遠端peer是否對本client提供的資料感興趣,這由遠端peer通知本client,當本client不choke它時,遠端peer就開始請求塊(block),這也意味著
    本程式碼中PcPeer

    需要記錄本client是否對遠端peer感興趣,以及它是否choke/unchoke遠端peer

客戶端連線開始時的初始狀態為choke和not interstered(不感興趣)

當本client對遠端peer感興趣,並且該遠端peer沒有choke時,那麼本client就可以從遠端peer下載塊(block)
當本client沒有choke遠端peer,而且該遠端peer對本client感興趣時,本client就會上傳塊(block)
這也就意味著,在建立連線後,本client必須不斷的通知遠端peer,告訴遠端peer,是否對它還感興趣
本和遠端peer的狀態資訊必須保持最新,即使本client被choke,這樣才能保證,當遠端peer不再choke本client時,本client可以開始下載(反之亦然)

對端通訊協議

對端之間要進行通訊,需要使用P2P通訊協議
本程式碼位於

bool CPcPeer::pack(char *pack, int &len, HOSTHANDLE& addr)
bool CPcPeer::unpack(char *pack, const int len)

P2P協議的訊息型別如下:

// 下述為0x0f下的
#define P2P_CONTROL_INV  ((char)0x01)    //連線請求
#define P2P_CONTROL_PAV  ((char)0x02)    //連線響應
#define P2P_CONTROL_CMD  ((char)0x03)    //命令
#define P2P_CONTROL_DAT  ((char)0x04)    //資料
#define P2P_CONTROL_LIV  ((char)0x05)    //心跳
// 下述為0xf0下的
#define P2P_MASK_CHK     ((char)0x80)        //CHOKE標記掩碼
#define P2P_MASK_REQ     ((char)0x40)        //REQUEST標記掩碼
#define P2P_MASK_HAV     ((char)0x20)        //HAVE標記掩碼

PcPeer的step狀態為

enum {ST_NONE, ST_PEND, ST_LINK, ST_TRAN};

初始本client和遠端peer均為ST_NONE狀態
step狀態的轉換圖如下:
step狀態的轉換圖
1.本client傳送連線請求報文後,本client狀態更新為ST_PEND
遠端peer收到連線請求協議後,遠端peer的狀態更新為ST_LINK
2.遠端peer傳送連線請求回覆協議,遠端peer的狀態更新為ST_TRAN
3.本client收到遠端peer發回的連接回復協議,本client狀態由ST_PEND更新為ST_TRAN
4.此後本client和遠端peer狀態均為ST_TRAN

協議流程圖如下:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

任務檔案

  1. http:serve的地址列表
  2. info_hash:任務檔案中info部分的sha校驗碼,即釋出的種子資料
  3. peer_id:自身的標識
  4. port:提供上傳的埠號
  5. ip:你的ip地址,沒有的話serve會自己找到
  6. uploaded、dowloaded:你上傳和下載了多少,伺服器可以用它做流量分析
  7. left:還要下載多少位元組
  8. event:狀態,告訴服務端你是準備開始下載,還是停止,或下載完成了
    BT在開啟一個任務檔案後,先選擇檔案儲存在哪裡,然後判斷檔案不存在的話就建立新檔案,存在的話就用sha-1去校驗檔案,如果校驗錯誤,說明是未下載的檔案,就可以實現續傳

伺服器

  1. 服務端會使用info_hash查詢列表,同時,根據ip和port進行反向連線,以測試使用者是內網使用者還是公網使用者(如果是內網使用者,是無法連通的)
  2. 服務端的返回資訊中,有一個時間引數,用來告訴客戶端使用者隔多少時間一次服務端,因為BT的動態性非常高,1秒內,可能有成千的peer加入或退出,所以需要一個引數設定查詢頻率

分片選擇演算法

嚴格的優先順序
第一個策略是,一旦請求了某個分片的子片段,然後該分片餘下的子片段會優先被請求,這樣,可以儘可能獲得一個完整的分片

最少優先
對一個下載者來說,選擇下一個被下載分片時,通常選擇它的peer們所擁有的最少的那個分片,即最少優先,這種技術,確保了每個peer都擁有其他peer們最希望得到的那些片段,從而一旦有需要,上傳就可以開始

隨機的第一個片段
最少優先的一個例外是下載開始時,此時,下載者沒有任何分片可供上傳,所以需儘快獲得一個完整的分片,所以,第一個分片是隨機選擇的,直到第一個分片下載完成,才切換回最少優先的策略

最後階段模式
有時候,peer可能從一個速率很慢的peer那裡請求一個分片,為了防止這種情況,在最後階段,peer向所有的peer都發送某分片的子片段請求,一旦某些子片段到了,就向其他的peers傳送取消訊息,取消對這些子片段的請求,以避免頻寬浪費

請求的併發傳送

在BT中,很重要的一點是同時傳送多個請求,以避免單個請求的兩個片段傳送之間的延遲

peer傳送到serve的請求

  1. info_hash: 即種子hash
  2. peer_id:使用URL編碼的20位元組串,用於標識客戶端的唯一ID,由客戶端啟動時生成。這個ID可以是任意值,甚至可能是二進位制資料。目前沒有生成該ID的標準準則,儘管如此,實現者必須保證該ID對於本地主機是唯一的,因此可以通過包含程序ID和啟動時記錄的時間戳(timestamp)來達到這個目的
  3. port:客戶端正在監聽的埠號。為BitTorrent協議保留的埠號是6881-6889
  4. uploaded:peer已上傳的總量(從start事件開始)
  5. downloaded:peer已下載的總量(從start事件開始)
  6. left:客戶端還未下載的位元組數,以十進位制表示
  7. compact:設定為1,表示客戶端接收壓縮的響應包,這時peers列表將被peers串替換,peers串中每個peer佔6個位元組(前4表示ip,後2表示埠)
  8. numwant(可選):客戶端希望從serve接受的peers數,如未設定,預設為50
  9. ip(可選):客戶端的IP地址,只有當請求的IP地址不是客戶端的IP地址時,才需要此引數(客戶端通過代理的方式互動)
  10. key:當客戶端ip改變時,可用該標識來證明自己的身份
  11. event:如果指定,必須是started/completed/stoped和空之中的一個,如果一個請求不指定event,表示它只是每隔一段時間傳送的請求,event取值如下
  • started:第一個傳送到serve的請求,其event必須為此值
  • stopped:如果正常關閉客戶端,必須發該事件到serve
  • completed: 如果下載完成,必須傳送該事件到serve,如果客戶端啟動前,就已完成下載,則沒必要傳送,serve僅僅基於此事件增加已完成的下載數
    event指定了值,但值為空,那麼它和event不指定值的效果一樣(定期傳送)

serve回覆peer的請求

  1. failure reason:如果包含這個鍵(key),那麼其他的鍵(key)就不會出現,該鍵(key)對應的值是一個可讀的錯誤訊息,它告訴使用者的請求為什麼會失敗
  2. interval:指定peer間隔傳送請求的時間
  3. min interval(可選):最小的請求間隔,表示peer不能在這個時間間隔內向track重發請求
  4. complete:完成整個檔案下載的peers數(做種數)
  5. incomplete:未完成檔案下載的peers數(非做種數)
  6. peers:peers列表
    peer列表數一般控制在30-55之間,當一個分片下載完成後,客戶端需要將have訊息傳送給大部分活躍的peers,結果是廣播通訊的代價和peers數目成正比,當這個數高於25以後,新加入的peers已不太可能提升下載速度

Peer端互動

一個客戶端(client)必須維持其與每一個遠端peer(端)連線的狀態資訊:
  1. choked: 遠端peer(端)是否已經choke本客戶端。當一個peer(端) choke本客戶端後,它是在通知本客戶端,除非它unchoke本客戶端,否則它不會應答該客戶端所發出的任何請求。本客戶端也不應該試圖向遠端peer傳送資料請求,並且應該認為所有沒有應答的請求已經被遠端peer丟棄。
  2. interested: 遠端peer(端)是否對本客戶端提供的資料感興趣。這是遠端peer在通知本客戶端,當本客戶端unchoke他們時,遠端客戶端將開始請求塊(block)。
    注意這也意味著本客戶端需要記錄它是否對遠端peer(端)感興趣,以及它是否choke/unchoke遠端peer。因此真正的列表看起來像這樣:
  • am_choking: 本客戶端正在choke遠端peer
  • am_interested: 本客戶端對遠端peer感興趣
  • peer_choking: 遠端peer正choke本客戶端
  • peer_interested: 遠端peer對本客戶端感興趣。
    客戶端連線開始時狀態是choke和not interested(不感興趣)。換句話就是:
    l am_choking = 1
    l am_interested = 0
    l peer_choking = 1
    l peer_interested = 0
    當一個客戶端對一個遠端peer感興趣並且那個遠端peer沒有choke這個客戶端,那麼這個客戶端就可以從遠端peer下載塊(block)。當一個客戶端沒有choke一個peer,並且那個peer對這個客戶端這個感興趣時,這個客戶端就會上傳塊(block)。
    客戶端必須不斷通知它的peers,它是否對它們感興趣,這一點是很重要的。客戶端和每個端的狀態資訊必須保持最新,即使本客戶端被choke。這允許所有的peer知道,當它們unchoke該客戶端後,該客戶端是否開始下載(反之亦然)。

握手

握手是一個必需的報文,並且必須是客戶端傳送的第一個報文。該握手報文的長度是(49+len(pstr))位元組
握手的訊息格式:handshake: <pstrlen><pstr><reserved><info_hash><peer_id>

  1. pstrlen: 的字串長度,單個位元組。
  2. pstr: 協議的識別符號,字串型別。
  3. reserved: 8個保留位元組。當前的所有實現都使用全0.這些位元組裡面的每一個位元組都可以用來改變協議的行為。
  4. info_hash: 種子hash
  5. peer_id: 用於唯一標識客戶端的20位元組字串。
    連線的發起者應該立即傳送握手報文,一旦接收方看到握手報文中的info_hash部分,接收方必須儘快響應
    如果一個客戶端接收到一個握手報文,並且該客戶端沒有服務這個報文的info_hash,那麼該客戶端必須丟棄該連線。
    如果一個連線發起者接收到一個握手報文,並且該報文中peer_id與期望的peer_id不匹配,那麼連線發起者應該丟棄該連線

報文

接下來協議的所有報文采用如下的結構:<length prefix><message ID><payload>。length prefix(長度字首)是一個4位元組的大端(big-endian)值。message ID是單個十進位制值。playload與訊息相關。

keep-alive

格式 :<len=0000>
keep-alive訊息是一個0位元組的訊息,將length prefix設定成0。沒有message ID和payload。如果peers在一個固定時間段內沒有收到任何報文(keep-alive或其他任何報文),那麼peers應該關掉這個連線,因此如果在一個給定的時間內沒有發出任何命令的話,peers必須傳送一個keep-alive報文保持這個連線啟用。通常情況下,這個時間是2分鐘

choke

格式 :<len=0001><id=0>
choke報文長度固定,並且沒有payload。

unchoke

格式 :<len=0001><id=1>
unchoke報文長度固定,並且沒有payload。

interested

格式 :<len=0001><id=2>
interested報文長度固定,並且沒有payload。

not interested

格式 :<len=0001><id=3>
not interested報文長度固定,並且沒有payload。

have

格式 :<len=0005><id=4><piece index>
have報文長度固定。payload是piece(片)的從零開始的索引,該片已經成功下載並且通過hash校驗。

實現者注意:實際上,一些客戶端必須嚴格實現該定義。因為peers不太可能下載他們已經擁有的piece(片),一個peer不應該通知另一個peer它擁有一個piece(片),如果另一個peer擁有這個piece(片)。最低限度”HAVE suppresion”會使用have報文數量減半,總的來說,大致減少25-35%的HAVE報文。同時,給一個擁有piece(片)的peer傳送HAVE報文是值得的,因為這有助於決定哪個piece是稀缺的。
一個惡意的peer可能向其他的peer廣播它們不可能下載的piece(片)。

bitfield

格式 : <len=0001+X><id=5><bitfield>
bitfield報文可能僅在握手序列傳送之後,其他訊息傳送之前立即傳送。它是可選的,如果一個客戶端沒有piece(片),就不需要傳送該報文。
bitfield報文長度可變,其中x是bitfield的長度。payload是一個bitfield,該bitfield表示已經成功下載的piece(片)。第一個位元組的高位相當於piece索引0。設定為0的位表示一個沒有的piece,設定為1的位表示有效的和可用的piece。末尾的冗餘位設定為0。
長度不對的bitfield將被認為是一個錯誤。如果客戶端接收到長度不對的bitfield或者bitfield有任一冗餘位集,它應該丟棄這個連線。

request

格式 : <len=0013><id=6><index><begin><length>
request報文長度固定,用於請求一個塊(block)。payload包含如下資訊:

  1. index: 整數,指定從零開始的piece索引。
  2. begin: 整數,指定piece中從零開始的位元組偏移。
  3. length: 整數,指定請求的長度。

piece

格式 :<len=0009+X><id=7><index><begin><block>
piece報文長度可變,其中x是塊的長度。payload包含如下資訊:
1. index: 整數,指定從零開始的piece索引。
2. begin: 整數,指定piece中從零開始的位元組偏移。
3. block: 資料塊,它是由索引指定的piece的子集。

cancel

格式 :<len=0013><id<=8><index><begin><length>
cancel報文長度固定,用於取消塊請求。playload與request報文的playload相同。一般情況下用於結束下載。

UDP打洞
NAT(Network Address Translator)有三種:
a)Full Cone NAT(完全圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000 ,192.168.0.8可以收到任意外部主機發到1.2.3.4:62000的資料報。
b)Address Restricted Cone NAT (地址限制圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000,只有當內部主機192.168.0.8先給伺服器C 6.7.8.9傳送一個數據報後,192.168.0.8才能收到6.7.8.9傳送到1.2.3.4:62000的資料報。
c)Port Restricted Cone NAT(埠限制圓錐型):從同一私網地址埠192.168.0.8:4000發至公網的所有請求都對映成同一個公網地址埠1.2.3.4:62000,只有當內部主機192.168.0.8先向外部主機地址埠6.7.8.9:8000傳送一個數據報後,192.168.0.8才能收到6.7.8.9:8000傳送到1.2.3.4:62000的資料報。
三者有個共同點,傳送資料時,對映的埠是固定的,然後可以通過固定埠和Peer通訊

UDP打洞的過程大致如此:

1、雙方都通過UDP與伺服器通訊後,閘道器預設就是做了一個外網IP和埠號 與你內網IP與埠號的對映,這個無需設定的,伺服器也不需要知道客戶的真正內網IP

2、使用者A先通過伺服器知道使用者B的外網地址與埠

3、使用者A向用戶B的外網地址與埠傳送訊息,

4、在這一次傳送中,使用者B的閘道器會拒收這條訊息,因為它的對映中並沒有這條規則。

5、但是使用者A的閘道器就會增加了一條允許規則,允許接收從B傳送過來的訊息

6、伺服器要求使用者B傳送一個訊息到使用者A的外網IP與埠號

7、使用者B傳送一條訊息,這時使用者A就可以接收到B的訊息,而且閘道器B也增加了允許規則,允許接收從A傳送過來的訊息

8、之後,由於閘道器A與閘道器B都增加了允許規則,所以A與B都可以向對方的外網IP和埠號傳送訊息。
這裡寫圖片描述

BT協議1.0

BitTorrent Protocol Specifications v1.0 翻譯
原文(原始版本):http://www.bittorrent.org/protocol.html
更詳細的版本:http://wiki.theory.org/BitTorrentSpecification
注:
1) 本文是原始版本的翻譯,如果有晦澀不清的地方,請參考上面的第二個link(更詳細的版本)。
2) 因為沒有任何實踐,完全是基於文件的自己的理解,所以可能會有翻譯和理解錯誤的地方。
3) 因為是老版本的協議原文,所述內容和現在實際的BT客戶端肯定會有出入。
4) 協議擴充套件和DHT相關介紹在官網(http://www.bittorrent.org)上都有,需要請參考。

BitTorrent協議規範1.0
BitTorrent是一種用來傳輸檔案的協議。它通過URL來識別被傳輸的檔案並且被設計成能夠無縫的整合到網路裡。 它相對於單純的HTTP協議的優勢是當多個下載者同時下載同一個檔案時,下載者之間可以互相傳輸檔案內容(注:提高了下載速度),這使得被下載源(注:也就是指通常http下載時的http server)能夠在只增加適量(少量/合理)負載的情況下支援非常大數量的下載者。

一個BitTorrent檔案傳輸系統由以下幾部分構成:
* 一個普通的web伺服器
* 一個靜態的元資訊檔案(注:也就是.torrent檔案)
* 一個BitTorrent的Tracker(注:也就是Tracker伺服器:用來追蹤當前所有下載者資訊的伺服器。)
* 一個原始的下載者(注:即種子。或者叫原始上傳者。在BT的世界裡,下載者也是上傳者。)
* 終端使用者的網頁瀏覽器(注:用來下載torrent檔案)
* 終端使用者下載者(注:可以理解為BT的Client端)
理想的情況下應該有很多的終端使用者在下載同一個檔案。(注:實際情況也正是如此!)

在網路上提供(釋出)一個BT檔案以供下載,需要進行以下的步驟:
1. 啟動一個Tracker伺服器(通常情況下,已經有一個Tracker伺服器正在執行)。
2. 啟動一個普通的web伺服器,比如apache,通常應該已經有一個web伺服器在執行。
3. 在web伺服器上將副檔名為.torrent檔案的mimetype關聯為application/x-bittorrent(注:使客戶端下載後知道用什麼應用程式開啟.torrent檔案)。
4. 根據原始檔(注:將要下載的檔案)和Tranker伺服器的URL生成元資訊檔案(.torrent檔案)。
5. 將生成的元資訊檔案(.torrent檔案)放到web伺服器上。
6. 在某個網頁上增加該元資訊檔案(.torrent檔案)的下載link。
7. 啟動一個擁有完整檔案的下載者(即源)(注:這時我們有了第一個上傳源,並且它有完整的檔案,也就是通常所說有一個種子)。

要開始通過BT下載檔案,需要進行以下的步驟:
1. 安裝BitTorrent(或者已經裝好了)
2. 瀏覽網頁(注:尋找你下載的檔案的.torrent檔案)
3. 點選.torrent檔案的link(注:這是你的BT程式應該會自動執行)
4. 選擇下載檔案的儲存路徑,或者繼續下載以前未下載完成的檔案。
5. 等待下載完成。
6. 結束BitTorrent程式(它將繼續上傳直到你手動停止它)。(注:結束前你就是一個種子)

傳輸的內容按如下方式編碼(注:該編碼方式稱為bencode):
* 字串編碼為:其長度的10進位制數加冒號加字串本身。例如:4:span表示字串’spam’
* 數字編碼為’i’加10進位制數加’e’。例如:i3e表示數字3,而i-3e表示數字-3。數字沒有長度限制。i-0e是非法的。所有以0開頭的編碼,比如i03e,都是非法的,除了i0e,很自然的它表示數字0。
* 列表(List)編碼為字母’l’加列表的元素(元素同樣也是bencoded過的)加’e’。例如l4:spam4:eggs表示列表[‘spam’, ‘eggs’]。
* 字典(Dictionary)編碼為字母’d’加key加value加’e’。例如d3:cow3:moo:4spam4:eggse表示{‘cow’:’moo’, ‘spam’:’eggs’},而d4:spaml1:a1:bee表示{‘spam’: [‘a’, ‘b’]}。key必須是字串,並且必須按照排序後的順序出現(按照key的原字串排序,不是字母順序)。(注:sorted as raw strings應該是指基於binary compare的排序,而不是按照某種語言,比如英文,法文,字面意思/邏輯上的順序)

元資訊檔案(.torrent檔案)是一個以bencoded方式編碼過的字典,該字典包含以下的key(及key對應的value):
announce
Tracker的URL。
info
該key的value是一個map,包含以下的key(及其value):
name:其值是一個string,建議了被下載檔案(或者資料夾)用什麼名字儲存。它僅僅是一個建議值。
piece length:其值是一個數字,定義了每個檔案塊的大小,單位是byte。
為了資料傳輸的方便,檔案被分成固定大小的塊(piece),除了最後一個檔案塊,因為很可能除不盡。
piece length通常是2的冪,最常用的是2的18冪=256KB(3.2以前的版本用的是2的20次冪=1MB作為預設值)。
pieces:其值是一個長度是20的倍數的string,每一段string(長度為20)內容是其對應順序的檔案塊的SH1的雜湊值。
還有兩個key是length和files,這兩個key不能同時出現,也不能都不出現(也就是說必須也只能出現一個)。
如果length出現則表示下載的是單個檔案,否則就表示下載的是多個檔案:以資料夾的形式。
單個檔案的情況下,length就表示該檔案的大小,單位是byte。
考慮到其他key的目的,多檔案也作為一個檔案處理;按照它們在資料夾裡出現的順序連線成一個檔案。
這種情況下,files的值是一個字典的列表(譯者注:每一個字典代表一個檔案),每個字典包括以下的key(及其value):
length:其值是數字,定義檔案的長度,單位是byte。
path:其值是string的列表,每個string表示子資料夾的名字,最後一個string表示檔案的名字(長度為零的列表是錯誤的)。
單檔案的情況下,name就表示這個檔案的名字。多檔案時,name表示資料夾的名字。

向Tracker伺服器以Get方式傳送的請求中應包含以下的Key:
info_hash
.torrent檔案裡info欄位(bencoded編碼後的info欄位)的SHA1雜湊值,長度為20位元組。
注意info欄位只是.torrent檔案的一個子串(注:.torrent檔案本身就是一個字串)。
這20位元組的雜湊值必須要進行URL編碼。
peer_id
長度為20的字串,用作標識下載端的ID。
由每一個下載端在開始下載的時候隨機生成(注:可理解為每次下載不同檔案時都生成一個peer_id)。
該值也必須進行URL編碼(注:另一份更加詳細的文件中則註明該值不能進行URL編碼)。
ip
可選項。
表明當前peer所在的IP地址(或者DNS名)(注:應該是指DNS解析前的主機名)。
通常用於第一個種子,如果它和Tracker伺服器在同一臺機器上。
port
當前peer所偵聽的埠號。
預設的行為是依次嘗試偵聽埠6881到6889,如果所有這些埠都被佔用,則放棄偵聽(注:也就是放棄下載)。
uploaded
總上傳大小,用十進位制ASCII字元表示。(注:從傳送”started”事件開始計算,單位應該是位元組。)
downloaded
總下載大小,用十進位制ASCII字元表示。(注:從傳送”started”事件開始計算,單位應該是位元組。)
left
未下載大小(注:還需要下載的內容),用十進位制ASCII字元表示(注:單位應該是位元組)。
注意這個數值不可以用檔案總位元組數和已經下載位元組數之差來計算。
某些已經下載但未完成檔案塊會因為不能通過一致性檢查而被重新下載。
(注:應該是計算某檔案塊的雜湊值,如果和已知的,即下載完成的該塊的雜湊值不一樣,則一致性檢查失敗。
從而認為該檔案塊沒有下載完畢,需要重新下載。)
event
可選項。
其值可以是started,completed,stopped(或者是empty,值是empty和沒有該key是一樣的)。
如果沒有該key,則本次request被認為是普通的定期request中的一次。
當下載開始時,第一個request必須包括該key並且其值必須是started。
當下載完成時,必須傳送一個request包含該key並且值為completed。
但是在檔案下載完成後再開始該任務時(注:其實就是做種子),不能傳送包含completed的request。
當下載停止時,必須傳送包含stopped的request給Tracker伺服器。
Tracker伺服器的response也是以bencoded方式編碼過的字典。如果response中包含有名為failure reason的key,那麼該key的值是一個人可以理解的字串(注:含有語義的字串)用來通知發出請求的BT客戶端該請求失敗的原因,同時此時該response中不需要有其它任何key。否則,response中必須包含有兩個key:interval,值為數字,單位是秒。用來告知BT客戶端通常的定期發出的request之間的最小間隔時間。peers,值為字典的列表。其中每個字典包含以下的key:peer id, ip和port。他們的值依次是BT客戶端的ID,IP地址或者DNS名,埠號。注意當有事件發生或者需要知道更多的peer時,BT客戶端可以向Tracker傳送非定期的request。

如果你需要對元資訊檔案(.torrent檔案)或者傳送給Tracker伺服器的request的內容作任何擴充,請與Bram Cohen協商以確保所有的擴充套件是相容的。

BitTorrent的peer協議構建在TCP協議之上。它運作效率很高同時不需要做任何socket的設定。

peer之間的連線時對等的。從peer連線的兩個方向上看,傳送的訊息時一樣的,同時資料可以向連線的任何一個方向上傳送。

peer協議是以元資訊檔案(.torrent檔案)裡描述的排序的檔案塊(file piece)這一概念為基礎的,排序的序號從0開始。當某個peer下載完某個檔案塊,並驗證其hash值正確後,該peer就向與它連線著的其它peer宣佈它擁有該檔案塊。

對於每個連線連線來說,每一端的peer應該是4種狀態之一(可用2bit來定義這4種狀態):一端是interested或者not interested,則另一端是choking或者unchoking。反之亦然。Choking表示直到unchoking發生之前,都不會有任何資料傳送給連線另一端的peer。關於Choke的原因和相關使用技巧將在本文的後面解釋。

注:為了便於後面的理解,有必要先解釋一下choke和interest。舉例:檔案被分為0,1,2,3,4這5個檔案塊。現在有兩個peer建立了連線,分別叫A和B。以下的解釋是站在A的立場上進行的。
interested - 如果A沒有塊1,而B有塊1,就是說B有A需要的塊。則A的狀態就是interested。
not interested - 如果A已經有塊1,而B只有塊1,就是說B沒有A需要的快。那麼A的狀態就是not interested。
choking - 如果因為某種原因,B將在某段時間內(懲罰期)不給A傳送任何資料,那麼在這段時間,B的狀態就是choking(choked)。
unchoking - 懲罰期過後,choking狀態解除,B的狀態就變為unchoking(unchoked)。

peer之間的連線上要發生資料傳輸,那麼peer的狀態就必須是一端interested,另一端unchoked。interested/not interested狀態必須時時進行更新。當某個peer需要下載某個檔案塊時,它應該向狀態時unchoked的peer明確表示它需要的檔案塊,同時忽視那些狀態時choked的peer。要正確地實現這個需要一些技巧(注:不是那麼簡單),但是一旦正確地實現了,就可以讓peer知道哪些與它相連的狀態是unchoked的peer會立刻開始給它傳送它需要的資料。

連線剛建立時,預設的狀態是自己是not interested,對方是choked。

當資料被傳輸時,BT客戶端需要維持在同一時間內始終有多個對不同檔案塊的request在排隊,以保證能夠取得最好的TCP(網路)效能(這被稱作pipelining:管道排隊)。另一方面,排隊的request不應該立刻被寫入TCP的緩衝區裡,該request佇列應該由BT客戶端在自己的記憶體中儲存,而不是儲存到應該用程式級別的網路緩衝區裡(注:也就是說應該BT客戶端自己維護這個佇列,而不是交給TCP協議去維護)。這樣做事為了當choke發生時,這些未傳送同時又已經沒必要傳送的request可以及時被拋棄掉。

peer wire協議由握手訊息開始,接著便是沒有結束標記的帶有長度字首的訊息(注:”length-prefixed messages”應該就是指按bencoded方式編碼過的字串。而”never-ending”應該是說如果需要結束,就直接斷開連線)。握手以”19”這個字串開始,後面緊接著是”BitTorrent protocol”這個字串。開頭的第一個字串是定長的(”19”這個字串的長度為2),這樣做希望其它新的協議也遵守這一規則以便新協議在連線後的一開始就能夠和當前協議區分開。(注:”BitTorrent protocol”這個字串的長度就是19,所以程式處理上應該首先固定讀取2個長度的字串,然後將其轉換為數字,接著讀取該數字表示的長度的字串,這個字串就是協議名稱。那麼通過連線後的第一個訊息,就可以知道對方使用的是什麼協議了。比如新的BT協議應該傳送如下的訊息:”22BitTorrent protocol v2”。)

所有後續傳送的數字應該使用4 bytes的big-endian編碼方式。(注:big-endian即高位位元組在後的編碼方式。比如這種編碼方式下,0x0010轉換為十進位制時應該這樣:0x0110 -> 0x1001 -> 9)

固定的訊息頭之後是8個位元組長的保留位元組,目前的實現裡是全0。如果你希望利用這些保留自己進行功能擴充,請與Bram Cohen協商以確保所有的擴充套件是相容的。

接著便是元資訊檔案裡面info的SHA1雜湊值,長度為20個位元組。(這和傳送給Tracker伺服器的request裡面的info_hash基本是一樣的,唯一的區別是這裡就是SHA1雜湊值,而info_hash還需要進行escape處理)(注:因為傳送Tracker伺服器的request是用HTTP的GET方式傳送的,所以必須進行escape處理。而這裡是直接TCP連線,用的是BT自己的協議,所以不需要escape處理)。如果雙方傳送的值不一樣,則切斷連線(注:如果hash值不一樣,則表示下載的不是同一個檔案,也就沒必要繼續了,所以這裡應該是要斷開連線的)。有一個例外就是如果某個BT客戶端希望通過一個埠下載多個檔案,那麼它可以先等對方傳送info的hash值,接收後,如果自己的下載列表裡包括該檔案,那麼傳送給對方同樣的info的hash值就可以了(注:如果沒有,則等同於雙方hash值不一樣,可以直接斷開連線)。

info的hash值傳輸完之後是20byte的peer id。每個下載端的peer id在向Tracker伺服器傳送request時被Tracker伺服器記錄下來,同時包含在Tracker伺服器傳送給BT客戶端的response裡面的peer id列表裡。所以如果接受方迴應的peer id和期待的peer id不一樣,就切斷連線。(注:具體解釋下:開始下載時,會向Tracker伺服器按照前面所述各式傳送request,接著從Tracker伺服器的response裡,可以獲得一系列[ip,peer id],那麼通過ip地址和對方連線後,按照本協議,對方會發送它的peer id過來,如果它發來的peer id和從Tracker伺服器上獲得的不一樣,就切斷連線。)

以上就是握手的過程,接下來就是帶有長度字首的訊息流(注:以bencoded方式編碼的訊息流)。長度為0的訊息時用作保持連線(注:類似於心跳幀),不會做進一步的處理。心跳訊息通常每2分鐘傳送一次(注:也就是說,當沒有有用的資料傳輸時,超時時間大約是2分鐘),但是當傳送資料訊息時,超時可能會發生的更快(注:葉就是說超時時間可能會短的多)。

所有的非心跳訊息以一個單位元組開頭,該位元組用來定義訊息型別。
可選的訊息型別如下:
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’必須是握手之後的第一個訊息,如果需要傳送的話(注:因為該訊息是可選的)。它的內容表明瞭傳送方peer已經擁有了哪些檔案塊:擁有的檔案塊的序號所對應的bit設定為1,沒有的設定為0。如果沒有任何檔案塊,則可以跳過該訊息(注:也就是不傳送)’。bitfield’的第一個位元組從高位到低位依次代表檔案塊0到7,第二個位元組代表8到15,其餘依次類推。結尾多餘的bit設定為0。(注:舉例來說如果一個檔案被分為20塊,某個peer開始下載時已經有了第1,4,7,18塊,那麼它傳送的’bitfield’訊息的內容就應該是010010010000000000100000)。

‘have’訊息的內容是一個數字:剛剛下載完成並且正確的通過hash值驗證的檔案塊的序號(注:該訊息用於通知連線那一頭的peer)。

‘request’消的內容包含一個序號,開始位置和長度。位置和長度的單位是位元組。長度通常是2的冪,除非因為到達檔案末尾。當前的實現使用的值是2的15次冪(注:即32KB。這是最早的值,現行版本的BT用的值請參考文章開頭註明的另外一遍更詳細的文件),如果對方peer要求的值大於2的17次冪(注:即128KB)則斷開連線。
(注:檔案塊並不是在一次訊息中完全被傳輸,實際上檔案塊又被細分為更小的塊,為了說明方便,這裡稱之為cell。所以通常下載完一個檔案塊需要多個request訊息,每個request訊息用來要求對方傳輸某個指定的cell。request訊息中的序號知名是哪個檔案塊,開始位置可以理解為cell的序號,長度是固定值,可以理解為cell的大小)。

‘cancel’ 訊息的內容和request訊息的內容一樣。’cancel’ 訊息通常只在檔案下載到最後的時候才有可能被髮送,這一時期訊息傳送/處理方式被稱為”遊戲結束模式”。當檔案下載將近尾聲時,通常都有這樣的趨勢:最後的少量資料會從某個單通道的modem線路上獲得(注:”off a single hosed modem line”不知道如何翻譯好),並且速度會非常的慢。所以為了儘快取得最後的這些資料塊,一旦要求這些資料塊的requests準備好了,就會把這些requests發給所有正在向我傳送資料的peer(要求他們傳送我需要的資料塊)。為了避免資料重發而導致的效率低下(注:這裡的資料重發指的是不同的peer都會向我傳送相同的資料),一旦我得到了某個資料塊,我就向其他所有peer傳送cancel訊息,告訴它們我已經得到了某個資料塊,不需要再向我傳送了。

‘piece’訊息的內容包含序號,開始和具體的資料。雖然訊息的內容裡沒有明確說明,但該訊息實際上是和request訊息相關的(注:可以理解為該訊息就是request訊息的response訊息,要來傳輸request訊息中要求的檔案資料)。需要注意的是有時候會收到非期望的’piece’訊息,比如choke和unchoke這兩個訊息交替發生的太快,或者訊息傳輸的太慢。
檔案塊的下載順序通常是隨機的,這樣可以避免某個peer擁有的檔案塊是和它相連的peer的超集或者子集,從而使下載合理並有效。

Choking之所以存在有幾個理由:首先是因為向許多連線同時傳送資料的時候,TCP的堵塞控制做的很差(所以BT客戶端需要自己做Choking,也就是堵塞,目的說白了就是要提高下載效率);另外choking也讓每個peer採用一種輪迴的演算法以保證它們能夠達到一致的下載速度(注:其實就是通過choking控制,避免和自己相連的某些peer下載速度快,而其它peer則一直很慢,簡而言之,就是為了公平)。

下面描述的choking演算法是當前已經應用的一個。很重要的一點是:所有新的演算法既要能在全部由新演算法構成的BT網路裡工作,也要能在大多數BT客戶端使用的是老演算法的網路裡工作。

一個好的演算法應該達到以下幾個標準。它應當能夠限制一定數量的上傳連線以獲得好的TCP效能。它應該避免頻繁的在choking和unchoking之間切換,這被叫做”纖維性顫動”。它應當報答那些讓它下載的peer(注:也就是不要choking下載源)。最後,它應當時不時地嘗試那些未曾unchoking過的peer(注:因為初始狀態是choking。),以便能夠找到比當前已經連線的效果更好的peer,這被稱作樂觀地unchoking。

當前實際應用的演算法通過延遲10秒去改變某個peer的choking狀態,以避免”纖維性顫動”。通過unchoking四個下載速度最好,並且是自己interested的peer,來報答這些peer和限制上傳連線。同時,上傳速度好,但不是interested的peer會被unchoking,但是如果它們變成interested狀態,那麼上傳速度最差的peer會被choking。如果自己擁有完整的檔案(注:自己是種子),那麼就用上傳速度來決定誰應該被unchoke。

最佳暢通是指:一直都會有一個peer被unchoking而不管它的上傳速度(如果它是自己interested,那麼就作為4個被允許的下載者之一)。最佳暢通每30秒迴圈一次。為了讓他們有機會下載一個完整的檔案塊(注:指某些最佳暢通的peer下載了某個檔案塊的一部分之後被choking了,為了能讓他們下載完這個檔案塊),下次和它的建立連線的可能性將會是迴圈中其它普通的暢通的三倍(注:即30秒後,要再次進行最佳暢通時,該peer被選中的可能性將會3倍於其它peer。如果這樣的peer多於一個,那麼它們之間的競爭則應該是平等的)。