Peer wire protocol (TCP)
概述
peer(端)協議使片(piece)的交換變得容易,片的描述請參考元信息文件。
注意:原來的規範在描述peer協議時,也使用術語piece“(片)”,但是這不同於元信息文件裏面的術語“piece(片)”,由於這個原因,在本規範中,將使用術語“塊(block)”來描述peers(端)之間交換的數據。
一個客戶端(client)必須維持其與每一個遠程peer(端)連接的狀態信息:
l choked: 遠程peer(端)是否已經choke本客戶端。當一個peer(端) choke本客戶端後,它是在通知本客戶端,除非它unchoke本客戶端,否則它不會應答該客戶端所發出的任何請求。本客戶端也不應該試圖向遠程peer發送數據請求,並且應該認爲所有沒有應答的請求已經被遠程peer丟棄。
l interested: 遠程peer(端)是否對本客戶端提供的數據感興趣。這是遠程peer在通知本客戶端,當本客戶端unchoke他們時,遠程客戶端將開始請求塊(block)。
注意這也意味着本客戶端需要記錄它是否對遠程peer(端)感興趣,以及它是否choke/unchoke遠程peer。因此真正的列表看起來像這樣:
l am_choking: 本客戶端正在choke遠程peer。
l am_interested: 本客戶端對遠程peer感興趣。
l peer_choking: 遠程peer正choke本客戶端。
l 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該客戶端後,該客戶端是否開始下載(反之亦然)。
數據類型
如果沒有用其他的方法指定,在peer wire協議中的所有整數都會編碼爲4個字節的大端(big-endian)值。這也包括在握手之後,所有報文(Message)的長度前綴。
報文流(Message flow)
(譯者注:因爲ICMP-Internet控制報文協議中的Message翻譯成報文,同時IP/TCP層中傳輸的數據都翻譯爲數據報,應用層傳輸的數據都翻譯成報文,因此在這裏Message翻譯成報文)
peer wire協議由一個初始的握手組成。握手之後,peers通過以長度爲前綴消息的交換進行通信。長度前綴就是上面描述的整數。
握手(HandShake)
握手是一個必需的報文,並且必須是客戶端發送的第一個報文。該握手報文的長度是(49+len(pstr))字節。
握手:handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
l pstrlen: <pstr>的字符串長度,單個字節。
l pstr: 協議的標識符,字符串類型。
l reserved: 8個保留字節。當前的所有實現都使用全0.這些字節裏面的每一個字節都可以用來改變協議的行爲。來自Bram的郵件建議應該首先使用後面的位,以便可以使用前面的位來改變後面位的意義。
l info_hash: 元信息文件中info鍵(key)對應值的20字節SHA1哈希。這個info_hash和在tracker請求中info_hash是同一個。
l peer_id: 用於唯一標識客戶端的20字節字符串。這個peer_id通常跟在tracker請求中傳送的peer_id相同(但也不盡然,例如在Azureus,就有一個匿名選項)。
在BitTorrent協議1.0版本,pstrlen = 19, pstr = “BitTorrent protocol”。
連接的發起者應該立即發送握手報文。如果接收方能夠同時地服務多個torrent,它會等待發起者的握手報文(torrent由infohash唯一標識)。儘管如此,一旦接收方看到握手報文中的info_hash部分,接收方必須儘快響應。tracker的NAT-checking特性不會發送握手報文的peer_id字段。
如果一個客戶端接收到一個握手報文,並且該客戶端沒有服務這個報文的info_hash,那麼該客戶端必須丟棄該連接。
如果一個連接發起者接收到一個握手報文,並且該報文中peer_id與期望的peer_id不匹配,那麼連接發起者應該丟棄該連接。注意發起者可能接收來自tracker的peer信息,該信息包含peer註冊的peer_id。來自於tracker的peer_id需要匹配握手報文中的peer_id。
peer_id
peer_id長20個字節。至於怎麼將客戶端和客戶端版本信息編碼成peer_id,現在主要有兩種慣例:Azureus風格和Shadow風格。
Azureus風格使用如下編碼方式:’-’, 緊接着是2個字符的client id,再接着是4個數字的版本號,’-’,後面跟着隨機數。
例如:'-AZ2060-'...
使用這種編碼風格的知名客戶端是:
l 'AG' - Ares
l 'A~' - Ares
l 'AR' - Arctic
l 'AT' - Artemis
l 'AX' - BitPump
l 'AZ' - Azureus
l 'BB' - BitBuddy
l 'BC' - BitComet
l 'BF' - Bitflu
l 'BG' - BTG (uses Rasterbar libtorrent)
l 'BP' - BitTorrent Pro (Azureus + spyware)
l 'BR' - BitRocket
l 'BS' - BTSlave
l 'BW' - BitWombat
l 'BX' - ~Bittorrent X
l 'CD' - Enhanced CTorrent
l 'CT' - CTorrent
l 'DE' - DelugeTorrent
l 'DP' - Propagate Data Client
l 'EB' - EBit
l 'ES' - electric sheep
l 'FC' - FileCroc
l 'FT' - FoxTorrent
l 'GS' - GSTorrent
l 'HL' - Halite
l 'HN' - Hydranode
l 'KG' - KGet
l 'KT' - KTorrent
l 'LC' - LeechCraft
l 'LH' - LH-ABC
l 'LP' - Lphant
l 'LT' - libtorrent
l 'lt' - libTorrent
l 'LW' - LimeWire
l 'MO' - MonoTorrent
l 'MP' - MooPolice
l 'MR' - Miro
l 'MT' - MoonlightTorrent
l 'NX' - Net Transport
l 'OT' - OmegaTorrent
l 'PD' - Pando
l 'qB' - qBittorrent
l 'QD' - QQDownload
l 'QT' - Qt 4 Torrent example
l 'RT' - Retriever
l 'RZ' - RezTorrent
l 'S~' - Shareaza alpha/beta
l 'SB' - ~Swiftbit
l 'SS' - SwarmScope
l 'ST' - SymTorrent
l 'st' - sharktorrent
l 'SZ' - Shareaza
l 'TN' - TorrentDotNET
l 'TR' - Transmission
l 'TS' - Torrentstorm
l 'TT' - TuoTu
l 'UL' - uLeecher!
l 'UM' - µTorrent for Mac
l 'UT' - µTorrent
l 'VG' - Vagaa
l 'WT' - BitLet
l 'WY' - FireTorrent
l 'XL' - Xunlei
l 'XT' - XanTorrent
l 'XX' - Xtorrent
l 'ZT' - ZipTorrent
另外還需要識別的客戶端有:
l 'BD' (例如: -BD0300-)
l 'NP' (例如: -NP0201-)
l 'SD' (例如: -SD0100-)
l 'wF' (例如: -wF2200-)
l 'hk' (例如: -hk0010-) 中國IP地址,IP address, unrequestedly sends info dict in message 0xA, reconnects immediately after being disconnected, reserved bytes = 01,01,01,01,00,00,02,01
Shadow風格使用如下編碼方式:一個用於客戶端標識的ASCII字母數字,多達五個字符的版本號(如果少於5個,則以’-’填充),緊接着是3個字符(通常是’---’,但也不總是這樣),最後跟着隨機數。版本字符串中的每一個字符表示一個0到63的數字。'0'=0, ..., '9'=9, 'A'=10, ..., 'Z'=35, 'a'=36, ..., 'z'=61, '.'=62, '-'=63。
你可以在這找到關於shadow編碼風格(包含關於版本字符串後的三個字符用法的習慣)的詳細說明。
例如:用於Shadow 5.8.11的’S58B-----‘...
使用這種編碼風格的知名客戶端是:
l 'A' - ABC
l 'O' - Osprey Permaseed
l 'Q' - BTQueue
l 'R' - Tribler
l 'S' - Shadow's client
l 'T' - BitTornado
l 'U' - UPnP NAT Bit Torrent
Bram的客戶端現在使用這種風格:'M3-4-2--' or 'M4-20-8-'。
BitComet使用不同的編碼風格。它的peer_id由4個ASCII字符’exbc’組成,接着是2個字節的x和y,最後是隨機字符。版本號中的x在小數點前面,y是版本號後的兩個數字。BitLord使用相同的方案,但是在版本號後面添加’LORD’。BitComet的一個非正式補丁曾經使用’FUTB’代替’exbc’。自版本0.59開始,BitComet peer id的編碼使用Azureus風格。
XBT客戶端也使用其特有的風格。它的peer_id由三個大寫字母’XBT’以及緊隨其後的代表版本號的三個ASCII數字組成。如果客戶端是debug版本,第七個字節是小寫字符’d’,否則就是’-‘。接着就是’-‘,然後是隨機數,大寫和小寫字母。例如:peer_id的開始部分爲'XBT054d-'表明該客戶端是版本號爲0.5.4的debug版本。
Opera 8預覽版和Opera 9.x發行版使用以下的peer_id方案:開始的兩個字符是’OP’,後面的四個數字是開發代號。接着的字符是隨機的小寫十六進制數字。
MLdonkey使用如下的peer_id方案:開始的字符是’-ML’,後面跟着點式版本,然後就是一個’-’,最後跟着隨機字符串。例如:'-ML2.7.2-kgjjfkd'。
Bit on Wheels使用模式'-BOWxxx-yyyyyyyyyyyy',其中y是隨機的(大寫字母),x依賴於版本。如果版本爲1.0.6,那麼xxx = AOC。
Queen Bee使用Bram的新風格:'Q1-0-0--' or 'Q1-10-0-'之後緊隨着隨機字節。
BitTyrant是Azureus的一個分支,在它的1.1版本,其peer id使用'AZ2500BT' + 隨機字節的方式。
TorrenTopia版本1.90自稱是或源自於Mainline 3.4.6。它的peer ID以'346------'開始。
BitSpirit有幾種編碼peer ID的方式。一種模式是讀取它的peer ID然後使用開始的八個字節作爲它peer ID的基礎來重新連接。它的實際ID使用'\0\3BS'(c 標記法)作爲版本3.x的前四個字節,使用'\0\2BS'作爲版本2.x的前四個字節。所有方式都使用'UDP0'作爲結尾。
Rufus使用它的十進制ASCII版本值作爲開始的兩個字節。第三個和第四個字節是'RS'。緊隨其後的是用戶的暱稱和一些隨機字節。
C3 Torrent的peer ID以’-G3’開始,然後追加多達9個表示用戶暱稱的字符。
FlashGet使用Azureus風格,但是前面字符是’FG’,沒有’-’。版本 1.82.1002 仍然使用版本數字 '0180'。
BT Next Evolution源自於BitTornado,但是試着模仿Azureus風格。結果是它的peer ID以’-NE’開始,接着是四個數字的版本號,最後就是以shadow peer id風格描述客戶端類型的三個字符。
AllPeers takes the sha1 hash of a user dependent string(這個不好翻譯,待譯),使用"AP" + version string + "-"代替開始的一些字符。
Qvod的id以四個字母"QVOD"開始,接着是4個十進制數字的開發代號(目前是” 0054”)。最後的12個字符是隨機的大寫十六進制數字。中國有一個修改版,該版本以隨機字節代替前四個字符。
許多客戶端全部使用隨機數或者隨機數後面跟12個全0(像Bram客戶端的老版本)。
報文(Messages)
接下來協議的所有報文采用如下的結構:<length prefix><message ID><payload>。length prefix(長度前綴)是一個4字節的大端(big-endian)值。message
ID是單個十進制值。playload與消息相關。
l keep-alive: <len=0000>
keep-alive消息是一個0字節的消息,將length prefix設置成0。沒有message ID和payload。如果peers在一個固定時間段內沒有收到任何報文(keep-alive或其他任何報文),那麼peers應該關掉這個連接,因此如果在一個給定的時間內沒有發出任何命令的話,peers必鬚髮送一個keep-alive報文保持這個連接激活。通常情況下,這個時間是2分鐘。
l choke: <len=0001><id=0>
choke報文長度固定,並且沒有payload。
l unchoke: <len=0001><id=1>
unchoke報文長度固定,並且沒有payload。
l interested: <len=0001><id=2>
interested報文長度固定,並且沒有payload。
l not interested: <len=0001><id=3>
not interested報文長度固定,並且沒有payload。
l 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(片)。Due to this attempting to model peers using this information is a bad idea.
l 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有任一冗餘位集,它應該丟棄這個連接。
l request: <len=0013><id=6><index><begin><length>
request報文長度固定,用於請求一個塊(block)。payload包含如下信息:
n index: 整數,指定從零開始的piece索引。
n begin: 整數,指定piece中從零開始的字節偏移。
n length: 整數,指定請求的長度。
l piece: <len=0009+X><id=7><index><begin><block>
piece報文長度可變,其中x是塊的長度。payload包含如下信息:
n index: 整數,指定從零開始的piece索引。
n begin: 整數,指定piece中從零開始的字節偏移。
n block: 數據塊,它是由索引指定的piece的子集。
l cancel: <len=0013><id<=8><index><begin><length>
cancel報文長度固定,用於取消塊請求。playload與request報文的playload相同。一般情況下用於結束下載。
l port: <len=0003><id=9><listen-port>
port報文由新版本的Mainline發送,新版本Mainline實現了一個DHT tracker。該監聽端口是peer的DHT節點正在監聽的端口。這個peer應該插入本地路由表(如果支持DHT tracker的話)。