Peer wire protocol (TCP)
概述
peer(端)協(xié)議使片(piece)的交換變得容易,片的描述請參考元信息文件。
注意:原來的規(guī)范在描述peer協(xié)議時,也使用術(shù)語piece“(片)”,但是這不同于元信息文件里面的術(shù)語“piece(片)”,由于這個原因,在本規(guī)范中,將使用術(shù)語“塊(block)”來描述peers(端)之間交換的數(shù)據(jù)。
一個客戶端(client)必須維持其與每一個遠(yuǎn)程peer(端)連接的狀態(tài)信息:
l choked: 遠(yuǎn)程peer(端)是否已經(jīng)choke本客戶端。當(dāng)一個peer(端) choke本客戶端后,它是在通知本客戶端,除非它unchoke本客戶端,否則它不會應(yīng)答該客戶端所發(fā)出的任何請求。本客戶端也不應(yīng)該試圖向遠(yuǎn)程peer發(fā)送數(shù)據(jù)請求,并且應(yīng)該認(rèn)為所有沒有應(yīng)答的請求已經(jīng)被遠(yuǎn)程peer丟棄。
l interested: 遠(yuǎn)程peer(端)是否對本客戶端提供的數(shù)據(jù)感興趣。這是遠(yuǎn)程peer在通知本客戶端,當(dāng)本客戶端unchoke他們時,遠(yuǎn)程客戶端將開始請求塊(block)。
注意這也意味著本客戶端需要記錄它是否對遠(yuǎn)程peer(端)感興趣,以及它是否choke/unchoke遠(yuǎn)程peer。因此真正的列表看起來像這樣:
l am_choking: 本客戶端正在choke遠(yuǎn)程peer。
l am_interested: 本客戶端對遠(yuǎn)程peer感興趣。
l peer_choking: 遠(yuǎn)程peer正choke本客戶端。
l peer_interested: 遠(yuǎn)程peer對本客戶端感興趣。
客戶端連接開始時狀態(tài)是choke和not interested(不感興趣)。換句話就是:
l am_choking = 1
l am_interested = 0
l peer_choking = 1
l peer_interested = 0
當(dāng)一個客戶端對一個遠(yuǎn)程peer感興趣并且那個遠(yuǎn)程peer沒有choke這個客戶端,那么這個客戶端就可以從遠(yuǎn)程peer下載塊(block)。當(dāng)一個客戶端沒有choke一個peer,并且那個peer對這個客戶端這個感興趣時,這個客戶端就會上傳塊(block)。
客戶端必須不斷通知它的peers,它是否對它們感興趣,這一點(diǎn)是很重要的?蛻舳撕兔總端的狀態(tài)信息必須保持最新,即使本客戶端被choke。這允許所有的peer知道,當(dāng)它們unchoke該客戶端后,該客戶端是否開始下載(反之亦然)。
數(shù)據(jù)類型
如果沒有用其他的方法指定,在peer wire協(xié)議中的所有整數(shù)都會編碼為4個字節(jié)的大端(big-endian)值。這也包括在握手之后,所有報(bào)文(Message)的長度前綴。
報(bào)文流(Message flow)
(譯者注:因?yàn)镮CMP-Internet控制報(bào)文協(xié)議中的Message翻譯成報(bào)文,同時IP/TCP層中傳輸?shù)臄?shù)據(jù)都翻譯為數(shù)據(jù)報(bào),應(yīng)用層傳輸?shù)臄?shù)據(jù)都翻譯成報(bào)文,因此在這里Message翻譯成報(bào)文)
peer wire協(xié)議由一個初始的握手組成。握手之后,peers通過以長度為前綴消息的交換進(jìn)行通信。長度前綴就是上面描述的整數(shù)。
握手(HandShake)
握手是一個必需的報(bào)文,并且必須是客戶端發(fā)送的第一個報(bào)文。該握手報(bào)文的長度是(49+len(pstr))字節(jié)。
握手:handshake: <pstrlen><pstr><reserved><info_hash><peer_id>
l pstrlen: <pstr>的字符串長度,單個字節(jié)。
l pstr: 協(xié)議的標(biāo)識符,字符串類型。
l reserved: 8個保留字節(jié)。當(dāng)前的所有實(shí)現(xiàn)都使用全0.這些字節(jié)里面的每一個字節(jié)都可以用來改變協(xié)議的行為。來自Bram的郵件建議應(yīng)該首先使用后面的位,以便可以使用前面的位來改變后面位的意義。
l info_hash: 元信息文件中info鍵(key)對應(yīng)值的20字節(jié)SHA1哈希。這個info_hash和在tracker請求中info_hash是同一個。
l peer_id: 用于唯一標(biāo)識客戶端的20字節(jié)字符串。這個peer_id通常跟在tracker請求中傳送的peer_id相同(但也不盡然,例如在Azureus,就有一個匿名選項(xiàng))。
在BitTorrent協(xié)議1.0版本,pstrlen = 19, pstr = “BitTorrent protocol”。
連接的發(fā)起者應(yīng)該立即發(fā)送握手報(bào)文。如果接收方能夠同時地服務(wù)多個torrent,它會等待發(fā)起者的握手報(bào)文(torrent由infohash唯一標(biāo)識)。盡管如此,一旦接收方看到握手報(bào)文中的info_hash部分,接收方必須盡快響應(yīng)。tracker的NAT-checking特性不會發(fā)送握手報(bào)文的peer_id字段。
如果一個客戶端接收到一個握手報(bào)文,并且該客戶端沒有服務(wù)這個報(bào)文的info_hash,那么該客戶端必須丟棄該連接。
如果一個連接發(fā)起者接收到一個握手報(bào)文,并且該報(bào)文中peer_id與期望的peer_id不匹配,那么連接發(fā)起者應(yīng)該丟棄該連接。注意發(fā)起者可能接收來自tracker的peer信息,該信息包含peer注冊的peer_id。來自于tracker的peer_id需要匹配握手報(bào)文中的peer_id。
peer_id
peer_id長20個字節(jié)。至于怎么將客戶端和客戶端版本信息編碼成peer_id,現(xiàn)在主要有兩種慣例:Azureus風(fēng)格和Shadow風(fēng)格。
Azureus風(fēng)格使用如下編碼方式:’-’, 緊接著是2個字符的client id,再接著是4個數(shù)字的版本號,’-’,后面跟著隨機(jī)數(shù)。
例如:'-AZ2060-'...
使用這種編碼風(fēng)格的知名客戶端是:
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風(fēng)格使用如下編碼方式:一個用于客戶端標(biāo)識的ASCII字母數(shù)字,多達(dá)五個字符的版本號(如果少于5個,則以’-’填充),緊接著是3個字符(通常是’---’,但也不總是這樣),最后跟著隨機(jī)數(shù)。版本字符串中的每一個字符表示一個0到63的數(shù)字。'0'=0, ..., '9'=9, 'A'=10, ..., 'Z'=35, 'a'=36, ..., 'z'=61, '.'=62, '-'=63。
你可以在這找到關(guān)于shadow編碼風(fēng)格(包含關(guān)于版本字符串后的三個字符用法的習(xí)慣)的詳細(xì)說明。
例如:用于Shadow 5.8.11的’S58B-----‘...
使用這種編碼風(fēng)格的知名客戶端是:
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的客戶端現(xiàn)在使用這種風(fēng)格:'M3-4-2--' or 'M4-20-8-'。
BitComet使用不同的編碼風(fēng)格。它的peer_id由4個ASCII字符’exbc’組成,接著是2個字節(jié)的x和y,最后是隨機(jī)字符。版本號中的x在小數(shù)點(diǎn)前面,y是版本號后的兩個數(shù)字。BitLord使用相同的方案,但是在版本號后面添加’LORD’。BitComet的一個非正式補(bǔ)丁曾經(jīng)使用’FUTB’代替’exbc’。自版本0.59開始,BitComet peer id的編碼使用Azureus風(fēng)格。
XBT客戶端也使用其特有的風(fēng)格。它的peer_id由三個大寫字母’XBT’以及緊隨其后的代表版本號的三個ASCII數(shù)字組成。如果客戶端是debug版本,第七個字節(jié)是小寫字符’d’,否則就是’-‘。接著就是’-‘,然后是隨機(jī)數(shù),大寫和小寫字母。例如:peer_id的開始部分為'XBT054d-'表明該客戶端是版本號為0.5.4的debug版本。
Opera 8預(yù)覽版和Opera 9.x發(fā)行版使用以下的peer_id方案:開始的兩個字符是’OP’,后面的四個數(shù)字是開發(fā)代號。接著的字符是隨機(jī)的小寫十六進(jìn)制數(shù)字。
MLdonkey使用如下的peer_id方案:開始的字符是’-ML’,后面跟著點(diǎn)式版本,然后就是一個’-’,最后跟著隨機(jī)字符串。例如:'-ML2.7.2-kgjjfkd'。
Bit on Wheels使用模式'-BOWxxx-yyyyyyyyyyyy',其中y是隨機(jī)的(大寫字母),x依賴于版本。如果版本為1.0.6,那么xxx = AOC。
Queen Bee使用Bram的新風(fēng)格:'Q1-0-0--' or 'Q1-10-0-'之后緊隨著隨機(jī)字節(jié)。
BitTyrant是Azureus的一個分支,在它的1.1版本,其peer id使用'AZ2500BT' + 隨機(jī)字節(jié)的方式。
TorrenTopia版本1.90自稱是或源自于Mainline 3.4.6。它的peer ID以'346------'開始。
BitSpirit有幾種編碼peer ID的方式。一種模式是讀取它的peer ID然后使用開始的八個字節(jié)作為它peer ID的基礎(chǔ)來重新連接。它的實(shí)際ID使用'\0\3BS'(c 標(biāo)記法)作為版本3.x的前四個字節(jié),使用'\0\2BS'作為版本2.x的前四個字節(jié)。所有方式都使用'UDP0'作為結(jié)尾。
Rufus使用它的十進(jìn)制ASCII版本值作為開始的兩個字節(jié)。第三個和第四個字節(jié)是'RS'。緊隨其后的是用戶的昵稱和一些隨機(jī)字節(jié)。
C3 Torrent的peer ID以’-G3’開始,然后追加多達(dá)9個表示用戶昵稱的字符。
FlashGet使用Azureus風(fēng)格,但是前面字符是’FG’,沒有’-’。版本 1.82.1002 仍然使用版本數(shù)字 '0180'。
BT Next Evolution源自于BitTornado,但是試著模仿Azureus風(fēng)格。結(jié)果是它的peer ID以’-NE’開始,接著是四個數(shù)字的版本號,最后就是以shadow peer id風(fēng)格描述客戶端類型的三個字符。
AllPeers takes the sha1 hash of a user dependent string(這個不好翻譯,待譯),使用"AP" + version string + "-"代替開始的一些字符。
Qvod的id以四個字母"QVOD"開始,接著是4個十進(jìn)制數(shù)字的開發(fā)代號(目前是” 0054”)。最后的12個字符是隨機(jī)的大寫十六進(jìn)制數(shù)字。中國有一個修改版,該版本以隨機(jī)字節(jié)代替前四個字符。
許多客戶端全部使用隨機(jī)數(shù)或者隨機(jī)數(shù)后面跟12個全0(像Bram客戶端的老版本)。
報(bào)文(Messages)
接下來協(xié)議的所有報(bào)文采用如下的結(jié)構(gòu):<length prefix><message ID><payload>。length prefix(長度前綴)是一個4字節(jié)的大端(big-endian)值。message ID是單個十進(jìn)制值。playload與消息相關(guān)。
l keep-alive: <len=0000>
keep-alive消息是一個0字節(jié)的消息,將length prefix設(shè)置成0。沒有message ID和payload。如果peers在一個固定時間段內(nèi)沒有收到任何報(bào)文(keep-alive或其他任何報(bào)文),那么peers應(yīng)該關(guān)掉這個連接,因此如果在一個給定的時間內(nèi)沒有發(fā)出任何命令的話,peers必須發(fā)送一個keep-alive報(bào)文保持這個連接激活。通常情況下,這個時間是2分鐘。
l choke: <len=0001><id=0>
choke報(bào)文長度固定,并且沒有payload。
l unchoke: <len=0001><id=1>
unchoke報(bào)文長度固定,并且沒有payload。
l interested: <len=0001><id=2>
interested報(bào)文長度固定,并且沒有payload。
l not interested: <len=0001><id=3>
not interested報(bào)文長度固定,并且沒有payload。
l have: <len=0005><id=4><piece index>
have報(bào)文長度固定。payload是piece(片)的從零開始的索引,該片已經(jīng)成功下載并且通過hash校驗(yàn)。
實(shí)現(xiàn)者注意:實(shí)際上,一些客戶端必須嚴(yán)格實(shí)現(xiàn)該定義。因?yàn)閜eers不太可能下載他們已經(jīng)擁有的piece(片),一個peer不應(yīng)該通知另一個peer它擁有一個piece(片),如果另一個peer擁有這個piece(片)。最低限度”HAVE suppresion”會使用have報(bào)文數(shù)量減半,總的來說,大致減少25-35%的HAVE報(bào)文。同時,給一個擁有piece(片)的peer發(fā)送HAVE報(bào)文是值得的,因?yàn)檫@有助于決定哪個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報(bào)文可能僅在握手序列發(fā)送之后,其他消息發(fā)送之前立即發(fā)送。它是可選的,如果一個客戶端沒有piece(片),就不需要發(fā)送該報(bào)文。
bitfield報(bào)文長度可變,其中x是bitfield的長度。payload是一個bitfield,該bitfield表示已經(jīng)成功下載的piece(片)。第一個字節(jié)的高位相當(dāng)于piece索引0。設(shè)置為0的位表示一個沒有的piece,設(shè)置為1的位表示有效的和可用的piece。末尾的冗余位設(shè)置為0。
長度不對的bitfield將被認(rèn)為是一個錯誤。如果客戶端接收到長度不對的bitfield或者bitfield有任一冗余位集,它應(yīng)該丟棄這個連接。
l request: <len=0013><id=6><index><begin><length>
request報(bào)文長度固定,用于請求一個塊(block)。payload包含如下信息:
n index: 整數(shù),指定從零開始的piece索引。
n begin: 整數(shù),指定piece中從零開始的字節(jié)偏移。
n length: 整數(shù),指定請求的長度。
l piece: <len=0009+X><id=7><index><begin><block>
piece報(bào)文長度可變,其中x是塊的長度。payload包含如下信息:
n index: 整數(shù),指定從零開始的piece索引。
n begin: 整數(shù),指定piece中從零開始的字節(jié)偏移。
n block: 數(shù)據(jù)塊,它是由索引指定的piece的子集。
l cancel: <len=0013><id<=8><index><begin><length>
cancel報(bào)文長度固定,用于取消塊請求。playload與request報(bào)文的playload相同。一般情況下用于結(jié)束下載。
l port: <len=0003><id=9><listen-port>
port報(bào)文由新版本的Mainline發(fā)送,新版本Mainline實(shí)現(xiàn)了一個DHT tracker。該監(jiān)聽端口是peer的DHT節(jié)點(diǎn)正在監(jiān)聽的端口。這個peer應(yīng)該插入本地路由表(如果支持DHT tracker的話)。