本文转自:https://blog.csdn.net/xxxxxx91116/article/details/8544370

本文仅作为应急备份存储,如有侵权可发送邮件删除。

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: 远程peerchoke本客户端。

l  peer_interested: 远程peer对本客户端感兴趣。

客户端连接开始时状态是chokenot 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,它会等待发起者的握手报文(torrentinfohash唯一标识)。尽管如此,一旦接收方看到握手报文中的info_hash部分,接收方必须尽快响应。trackerNAT-checking特性不会发送握手报文的peer_id字段。

如果一个客户端接收到一个握手报文,并且该客户端没有服务这个报文的info_hash,那么该客户端必须丢弃该连接。

如果一个连接发起者接收到一个握手报文,并且该报文中peer_id与期望的peer_id不匹配,那么连接发起者应该丢弃该连接。注意发起者可能接收来自trackerpeer信息,该信息包含peer注册的peer_id。来自于trackerpeer_id需要匹配握手报文中的peer_id

peer_id
peer_id20个字节。至于怎么将客户端和客户端版本信息编码成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个字符(通常是’---’,但也不总是这样),最后跟着随机数。版本字符串中的每一个字符表示一个063的数字。'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_id4ASCII字符’exbc’组成,接着是2个字节的xy,最后是随机字符。版本号中的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.4debug版本。

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-'之后紧随着随机字节。

BitTyrantAzureus的一个分支,在它的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 Torrentpeer 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 + "-"代替开始的一些字符。

Qvodid以四个字母"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 IDpayload。如果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报文长度固定。payloadpiece()的从零开始的索引,该片已经成功下载并且通过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报文长度可变,其中xbitfield的长度。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报文长度固定,用于取消块请求。playloadrequest报文的playload相同。一般情况下用于结束下载。

l  port: <len=0003><id=9><listen-port>

port报文由新版本的Mainline发送,新版本Mainline实现了一个DHT tracker。该监听端口是peer的DHT节点正在监听的端口。这个peer应该插入本地路由表(如果支持DHT tracker的话)。