peer之间的通信协议
peer之间的通信协议又称为peer wire protocal,即peer连线协议,它是一个基于TCP协议的应用层协议。
为了防止有的peer只下载不上传,BitTorrent协议建议,客户端只给那些向它提供最快下载速度的4个peer上传数据。简单地说就是谁向我提供下载,我也提供数据供它下载;谁不提供数据给我下载,我的数据也不会上传给它。客户端每隔一定时间,比如10秒,重新计算从各个peer处下载数据的速度,将下载速度最快的4个peer解除阻塞,允许这4个peer从客户端下载数据,同时将其他peer阻塞。
一个例外情况是,为了发现下载速度更快的peer,协议还建议,在任一时刻,客户端保持一个优化非阻塞peer,即无论该peer是否提供数据给客户端下载,客户端都允许该peer从客户端这里下载数据。由于客户端向peer上传数据,peer接着也允许客户端从peer处下载数据,并且下载速度超过4个非阻塞peer中的一个。客户端每隔一定的时间,如30秒,重新选择优化非阻塞peer。
当客户端与peer建立TCP连接后,客户端必须维持的几个状态变量如下表所示。
客户端必须维持的状态变量
状 态 变 量 | 含 义 |
am_chocking | 该值若为1,表明客户端将远程peer阻塞。此时如果peer发送数据请求给客户端,客户端将不会理会。也就是说,一旦将peer阻塞,peer就无法从客户端下载到数据;该值若为0,则刚好相反,即表明peer未被阻塞,允许peer从客户端下载数据 |
am_interested | 该值若为1,表明客户端对远程的peer感兴趣。当peer拥有某个piece,而客户端没有,则客户端对peer感兴趣。该值若为0,则刚好相反,即表明客户端对peer不感兴趣,peer拥有的所有piece,客户端都拥有 |
peer_chocking | 该值若为1,表明peer将客户端阻塞。此时,客户端无法从peer处下载到数据。该值若为0,表明客户端可以向peer发送数据请求,客户端将进行响应 |
peer_interested | 该值若为1,表明peer对客户端感兴趣。也即客户端拥有某个piece,而peer没有。该值若为0,表明peer对客户端不感兴趣 |
当客户端与peer建立TCP连接后,客户端将这几个变量的值设置为。
am_chocking = 1。
am_interested = 0。
peer_chocking = 1。
peer_interested = 0。
当客户端对peer感兴趣且peer未将客户端阻塞时,客户端可以从peer处下载数据。当peer对客户端感兴趣,且客户端未将peer阻塞时,客户端向peer上传数据。
除非另有说明,所有的整数型在本协议中被编码为4字节值(高位在前低位在后),包括在握手之后所有信息的长度前缀。
客户端与一个peer建立TCP连接后,首先向peer发送握手消息,peer收到握手消息后回应一个握手消息。
l 握手消息是一个长度固定为68字节的消息。消息的格式如下:
消息格式中一些参数的含义如下表所示。
握手消息
参 数 | 含 义 |
pstrlen | pstr的长度,该值固定为19 |
pstr | BitTorrent协议的关键字,即“BitTorrent protocol” |
reserved | 占8字节,用于扩展BT协议,一般这8字节都设置为0。有些BT软件对BT协议进行了某些扩展,因此可能看到有些peer发来的握手消息这8个字节不全为0,不过不必理会,这不会影响正常的通信 |
info_hash | 与发往Tracker的GET请求中的info_hash为同一个值,长度固定为20字节 |
peer_id与发往Tracker的GET请求中的peer_id为同一个值,长度固定为20字节。一般从peer_id可以识别出BT软件的类型,例如,某peer发来的握手消息中peer_id的前8个字节为“-AZ2060-”,则可以断定对方使用的是Azureus;若为“-BCxxxx-”,x为数字,则对方使用的是BitComet。
对于除握手消息之外的其他所有消息,其一般的格式为:
length prefix(长度前缀)占4个字节,指明message ID和payload的长度和。message ID(消息编号)占一字节,是一个10进制的整数,指明消息的编号。payload(负载),长度未定,是消息的内容。
l keep_alive消息:
keep_alive消息的长度固定,为4字节,它没有消息编号和负载。如果一段时间内客户端与peer没有交换任何消息,则与这个peer的连接将被关闭。keep_alive消息用于维持这个连接,通常如果2分钟内没有向peer发送任何消息,则发送一个keep_alive消息。
l choke消息:
choke消息的长度固定,为5字节,消息长度占4个字节,消息编号占1个字节,没有负载。该消息的功能是,发出该消息的peer将接收该消息的peer阻塞,暂时不允许其下载自己的数据。
l unchoke消息:
unchoke消息的长度固定,为5字节,消息长度占4个字节,消息编号占1个字节,没有负载。客户端每隔一定的时间,通常为10秒,计算一次各个peer的下载速度,如果某peer被解除阻塞,则发送unchoke消息。如果某个peer原先是解除阻塞的,而此次被阻塞,则发送choke消息。
l interested消息:
interested消息的长度固定,为5字节,消息长度占4个字节,消息编号占1个字节,没有负载。当客户端收到某peer的have消息时,如果发现peer拥有了客户端没有的piece,则发送interested消息告知该peer,客户端对它感兴趣。
l not interested消息:
not interested消息的长度固定,为5字节,消息长度占4个字节,消息编号占1个字节,没有负载。当客户端下载了某个piece,如果发现客户端拥有了这个piece后,某个peer拥有的所有piece,客户端都拥有,则发送not interested消息给该peer。
l have消息:
have消息的长度固定,为9字节,消息长度占4个字节,消息编号占1个字节,负载为4个字节。负载为一个整数,指明下标为index的piece,peer已经拥有。每当客户端下载了一个piece,即将该piece的下标作为have消息的负载构造have消息,并把该消息发送给所有与客户端建立连接的peer。
l bitfield消息:
bitfield消息的长度不固定,其中X是bitfield(即位图)的长度。当客户端与peer交换握手消息之后,就交换位图。位图中,每个piece占一位,若该位的值为1,则表明已经拥有该piece;为0则表明该piece尚未下载。具体而言,假定某共享文件共拥有801个piece,则位图为101个字节,位图的第一个字节的最高位指明第一个piece是否拥有,位图的第一个字节的第二高位指明第二个piece是否拥有,依此类推。对于第801个piece,需要单独一个字节,该字节的最高位指明第801个piece是否已被下载,其余的7位放弃不予使用。
l request消息:
request消息的长度固定,为17个字节,index是piece的索引,begin是piece内的偏移,length是请求peer发送的数据的长度。当客户端收到某个peer发来的unchoke消息后,即构造request消息,向该peer发送数据请求。前面提到,peer之间交换数据是以slice(长度为16KB的块)为单位的,因此request消息中length的值一般为16K。对于一个256KB的piece,客户端分16次下载,每次下载一个16K的slice。
l piece消息:
piece消息是另外一个长度不固定的消息,长度前缀中的9是id、index、begin的长度总和,index和begin固定为4字节,X为block的长度,一般为16K。因此对于piece消息,长度前缀加上id通常为00 00 40 09 07。当客户端收到某个peer的request消息后,如果判定当前未将该peer阻塞,且peer请求的slice,客户端已经下载,则发送piece消息将文件数据上传给该peer。
l cancel消息:
cancel消息的长度固定,为17个字节,len、index、begin、length都占4字节。它与request消息对应,作用刚好相反,用于取消对某个slice的数据请求。如果客户端发现,某个piece中的slice,客户端已经下载,而客户端又向其他peer发送了对该slice的请求,则向该peer发送cancel消息,以取消对该slice的请求。事实上,如果算法设计合理,基本不用发送cancel消息,只在某些特殊的情况下才需要发送cancel消息。
l port消息:
port消息的长度固定,为7字节,其中listen-port占两个字节。该消息只在支持DHT的客户端中才会使用,用于指明DHT监听的端口号,一般不必理会,收到该消息时,直接丢弃即可。