协议定义
Gnutella协议定义客户机通过网络通讯的方式。其中包括定义了通过客户机进行数据通讯的描述符号集和内部客户机相互交互的一些规则。以下是定义的内容:
描述定义
指令 |
说明 |
Ping指令 |
用于激活发现网络上的客户机。一个客户机收到一个Ping的描述符表示希望回应一个或多个Pong描述符。 |
Pong指令 |
用于回应Ping。包括一个被连接的Gnutella客户机的地址和他能提供的数据供共享的信息。 |
Query指令 |
首要的分布式网络检索机制。一个客户机收到一个Query描述符后,如果在自己的数据集中发现一个匹配的数据将回应一个QueryHit。 |
QueryHit指令 |
用于回应Query。这个描述符提供足够的信息来获取匹配Query请求的数据。 |
Push指令 |
一个用于允许防火墙中的客户端向网络提供基于文件的数据文件的机制。 |
一个Gnutella客户机通过与另一个当前在网络中的客户机建立连接来使自己与网络相连。获取另一个客户机的地址不在这个协议的定义中,这里将不作描述(客户机地址缓冲保存是当前用强制方式自动获得Gnutella客户机地址的方式)。
一旦网络上的另一个客户机的地址被获取,一个与该客户机的TCP/IP连接将被创建,以下的Gnutella连接请求字符串(ASCII编码)将被发送:
GNUTELLA CONNECT/<protocol version string>\n\n
<protocol version string>在当前版本中定义文ASCII字符串“0.4”(或者,同样可以是” \x30\x2e\x34”)是指当前的协议规范的版本。
注明:
1.这份文件代表事实的标准的Gnutella0.4协议,然而,有些协议的实现方案扩展了组成协议的描述符并在Gnutella网络上传送描述符时附加了额外的规则。已知的协议扩展列在本文件末尾的附录中,但可能有些在实际应用中出现的变量没有在文件中说明。
2.Gnutella的发音是new-tella
一个客户机愿意接受连接的话必须回应
GNUTELLA OK\n\n
任何其它的回应表示客户机不愿意接受连接。一个客户机拒绝连接可能有以下几种原因-一个客户机的连接缓冲池已经满了,或者他不支持同样版本的协议来作为一个响应客户机。仅此举例。
一旦一个客户机成功连接到网络上,他与其它客户机通讯通过发送和接收Gnutella协议描述字。每一个描述符前都有一个以下字节结构的描述头,如下所示:
注意:1.以下所有结构内字符次序都是在低位在后,除非另作说明。
2.所有的地址都是IP4格式
例如:
表示的IP地址为:208.17.50.4
Descriptor Header
DescriptorID网络描述符:16个字节的字符串唯一标示网络的描述符号。
Payload Descriptor负载描述符:
0x00 = Ping
0x01 = Pong
0x40 = Push
0x80 = Query
0x81 = QueryHit
TTL生存期:描述字符在删除前在Gnutella网络中向前传递的次数。每个客户端在将包向前传递前将TTL减一。当TTL等于0,描述符将不再被向前传递。
Hops描述符被向前传递的次数:作为一个描述符向前传递,头部的TTL和Hops字必须满足以下条件:
TTL(0) = TTL(i) + Hops(i)
Payload Length负载长度:表示紧接着头部后面的描述符部分的长度。下一个描述符头后的从头部算起的Payload Length字节数,也就是没有间隔或保留字在Gnutella的数据流中。
TTL是网络中唯一的描述过期的机制。客户机应该仔细检查收到的描述符的TTL区并必要时减少它的值。滥用TTL区将会导致没有必要的网络阻塞和差劲的网络性能。
Payload Length区是客户机查找输入流中下一个描述符的唯一可靠方式。Gnutella协议不提供一个“监视”字符串或任何其它的描述符同步的方式。因此,客户机应该严格保证每一个收到的描述符的Payload Length区的有效性(至少为固定长度的描述符)。如果一个客户机不能和输入的流同步,它应该断掉与这个输入流有关的来自发送方的客户机,不管是产生这个流还是向前传递这个流的无效的客户机。紧接着描述头的是一个有效装载包含以下之一的描述符:
Ping (0x00)
Ping描述符没有相关的有效装载和数据长度为0。一个Ping只是简单地有一个描述头表述,它的有效装载区是0x00和装载长度区为0x00000000。
一个客户机用Ping描述符
Pong (0x01)
Port
Port:同意接收响应的客户机的端口
IP Address:响应的客户机的地址(此数据区高位字节在后)
Number of Files Shared:本机共享文件的数量
Number of Kilobytes Shared:本机所有共享文件的空间大小,以K为单位
Query (0x80)
字节偏移0 1 2 …
Minimum Speed :最小响应速度,响应的客户机的速度必须在此速度之上( 以K/秒为单位)
Search criteria:查询关键字,一个零结尾的字符串。这个字符串的最大长度由描述头的Payload Length负载长度规定。
QueryHit (0x81)
Number of Hits:符合搜索条件的结果数
Port:能接受连接的客户机的端口
IP Address :响应客户机的地址(此数据区高位字节在后)
Speed :响应客户机的连线速度(以K/秒为单位)
Result Set :响应查询的结果集。其中包含一个Number_of_Hits的部分,其中每个都包含以下结构
File Index:一个数字,由响应的客户机指定,用来唯一标示响应的文件结果
File Size:与File index相符的文件的大小
File Name:已双零结尾的与File index相符的文件的名字
Result Set的长度由描述头的Payload Length负载长度规定。
Servent Identifier:一个16位的字符串用来唯一标示网络上的客户机。功能上用来标示客户机的网络地址。用在Push指令上。
QueryHit指令只有在收到一个Query指令后响应才发出。一个客户机只有在它严格符合查询关键字时才对一个Query指令进行响应。
Push (0x40)
Servent Identifier:一个16位的字符串用来唯一标示网络上的客户机,该客户机请求下载带有File_Index的文件。
File Index:下载目标客户机的文件的唯一标识,初始化的客户机应该根据返回的QueryHit指令的File_Index中的标识设置。
IP Address:下载带有File_Index的文件的客户机的地址(此数据区高位字节在后)
Port:下载带有File_Index的文件的客户机的端口
描述符路由
Gnutella网络的点对点本质要求客户机合适地路由网络(包括查询、查询响应、推送文件请求等)。一个好的客户机应该根据以下的规则路由协议的描述符:
文件下载
一旦一个客户机收到一个QueryHit描述符,它将初始化直接下载描述符的结果集其中的一个文件。文件将不通过Gnutella的网络进行下载,一个源客户机和目标客户机直接建立连接进行数据的传输。文件数据从来不会通过Gnutella网络进行传送。
文件下载协议是HTTP协议。初始化下载的客户机发送一个请求字符串到目标客户机,格式如下:
GET /get/<File Index>/<File Name>/ HTTP/1.0\r\n
Connection: Keep-Alive\r\n
Range: bytes=0-\r\n
User-Agent: Gnutella\r\n3\r\n
这里的<File Index>和<File Name>是一个QueryHit描述符结果集中的File Index/File Name对中的其中之一。例如,如果QueryHit描述符中包含入口
File Index |
2468 |
File Size |
4356789 |
File Name |
Foobar.mp3\x00\x00 |
那么一个通过这个入口下载这个文件的描述请求应该如下:
GET /get/2468/Foobar.mp3/ HTTP/1.0\r\n
Connection: Keep-Alive\r\n
Range: bytes=0-\r\n
User-Agent: Gnutella\r\n
服务器收到下载请求将回应于HTTP1.0兼容的头,例如:
HTTP 200 OK\r\n
Server: Gnutella\r\n
Content-type: application/binary\r\n
Content-length: 4356789\r\n
\r\n
文件数据随后将被读出,包括HTTP响应中提供的Gontent-length中描述的字节数。
Gnutella协议提供HTTP规范中的参数的支持,所以一个中断的下载可以按照中断的位置重连。
防火墙后的客户机
并非总是在初始化一个文件下载后都可以与Gnutella客户机建立直接连接。客户机可能在防火墙后并不允许通过它的Gnutella端口进入的连接。如果一个直接连接不能建立,客户机若想下载文件可能会请求共享文件的客户机采用“推送”方式来代替。一个客户机可以通过发送一个Push文件推送请求到发送QueryHit请求的客户机处来实现。作为Push请求目标的客户机(在客户机标志区标示一个Push的描述符)应该接收Push描述符,尝试建立一个新的TCP/IP连接到请求客户机(在Push描述符中标示有IP地址和端口)。如果直接连接不能建立,那么可能发起Push请求的客户机自己也在防火墙后。这种情况,文件传输将不能进行。
如果一个直接连接可以从防火墙后的客户机建立到发起Push请求的客户机,防火墙后的客户机应该立刻发送以下的:
GIV <File Index>:<Servent Identifier>/<File Name>\n\n
这里的<File Index>:和<Servent Identifier>是Push请求头中的的文件索引和客户机标示,<File Name>是本地文件表中文件索引为<File Index>的文件。客户机收到GIV请求头(Push请求者)应该从头中取出<File Index>和<File Name>并构造一个如下的HTTP GET请求:
GET /get/<File Index>/<File Name>/ HTTP/1.0\r\n
Connection: Keep-Alive\r\n
Range: bytes=0-\r\n
User-Agent: Gnutella\r\n3
\r\n
余下的下载过程和上面所述的“文件下载”内容一致。
可允许的用户-代理字符串由HTTP标准定义。客户机开发者不能对这里使用的值做自己的假定。其中的值“Gnutella”只是用来演示举例而已。
附录1: Gnutella 协议扩展
扩展的Query Hit( 描述更新03/15/2001) 首先以BearShare v1.3.0 为例,扩展的QueryHit 描述符通过在原先的Gnutella QueryHit 描述符的Servent ID标识符和双null结尾的文件名之间放置额外的数据进行扩展。 一个扩展的QueryHit 描述符将有以下的结构:
QueryHit (0x81)
BearShare的Trailer区的机构如下:
Trailer
Vendor Code
四个大小写敏感的字母代表一个供应商代号。已经确认的代号如下:
Open Data Size
包含Open Data 域的长度(以字节计算)
Open Data
包含2个单字节的标志域,,具有以下的数据分布和次序
flags
flagUploadSpeed=1 当且仅当flags2中的flagUploadSpeed标志有意义时
flagHaveUploaded=1 当且仅当flags2中的flagHaveUploaded标志有意义时
flagBusy=1 当且仅当flags2中的flagBusy标志有意义时
flagPush=1 当且仅当客户机处于防火墙后或者未接受进入的连接时
r=保留字,将来使用
flags2
flagUploadSpeed=1 当且仅当QueryHit描述符中的Speed域包含上10次上传的最高平均速率时
flagHaveUploaded=1 当且仅当客户机成功上传至少一次文件时
flagBusy=1 当且仅当客户机的上传插槽满时
flagPush=1当且仅当flags中的flagPush标志有意义时
r=保留字,将来使用
Private Data
未作正式文档说明的BearShare的数据,这个区域的数据长度可以根据以下决定
Query Hit Descriptor Payload Size-(Open Data Size+4+1)
开发者处理扩展的一种方式为
(1) 意识到一进来的QueryHit 可以或者可能不包含附加数据,在结果下落之后,和在Servent 标识符以前。 没有完整说明适合是可能在场的字节的数量存在,或者他们内容。
使用负载长度领域并且算字节,当他们被从这条溪读确定是否扩展字节在场。
(2) 如果他们是,从排除16 字节Servent 标识符的输入流中读取数据。
(3) 象往常一样处理QueryHit。
Gnotella
Gnotella客户端软件的版本至早在0.73(2000年7月30发布)时把额外的数据放置在QueryHit 描述符中。 根据Gnutella 0.4 协议说明,每一个QueryHit 描述符中的结果集要用双nul结尾。 Gnotella 可以把额外的数据安置在两nuls 之间。 虽然它的精确的数据格式未知,但是这些数据代表比特率,采样率,和MP3 文件的播放时间,通过结果集的入口处来描述。 如果结果集入口处所描述的不是一个MP3文件的话,没有数据被放置在nuls 之间。
在没有不利的结果的情况下,一些客户机通过简单得抛弃nuls之间的数据来扩展Gnotella的QueryHit描述符。 另外的一些客户机可能,由于发现结果中的数据没有向预期那样结束,不能正确得读取描述符。 一旦读错,引起后来一系列的误处理,并且它的连接可能会断开。