一旦一个servent收到一个QueryHit消息,它可能会建立由这个消息的结果集描述的文件集中一个文件的
下载。文件是在网络外部下载的,例如建立一个源和目标的直接连接以实现文件传输。文件数据从来不会
在Gnutella网络上传输。
文件下载协议是HTTP。建议使用HTTP1.1(RFC2616),但是HTTP1.0(RFC1945)也可以使用。完整而详细的定
义在RFC中。下面只包括最基本的内容。以下例子假设使用HTTP1.1协议。
初始化下载的servent发送一个下面窗口的一个request字符串到目标服务器
GET /get/<File Index>/<File Name> HTTP/1.1<cr><lf>
User-Agent: Gnutella<cr><lf>
Host: 123.123.123.123:6346<cr><lf>
Connection: Keep-Alive<cr><lf>
Range: bytes=0-<cr><lf>
<cr><lf>
其中<File Index>/<File Name>是QueryHit消息的结果集中的File Index/File Name对。例如如果一个QueryHit消息的结果集包含下列条目
File Index: 2468
File Size: 4356789
File Name: Foobar.mp3
那么这个条目定义的文件的下载请求会被初始化如下
GET /get/2468/Foobar.mp3 HTTP/1.1<cr><lf>
User-Agent: Gnutella<cr><lf>
Host: 123.123.123.123:6346<cr><lf>
Connection: Keep-Alive<cr><lf>
Range: bytes=0-<cr><lf>
<cr><lf>
Servents必须根据标准URL/URI编码规则对GET请求中的文件名进行编码。Servents必须接受URL编码的GET请求。因为有些 老Servents不支持编码,Servents应该接受未编码的请求,如果一个初始请求返回一个404 Not Found 错误,servents可能会尝试发送一个未编码的请求。
HTTP1.1要求有Host报头,而且指定了你要连接到的地址。通常接收servent并不要求这点,但这是协议所要求的。
HTTP标准定义了User-Agent字符串的允许值。servent开发者不能对这个值做任何假设。"Gnutella"只是用来解释用的。
接收到这个请求的服务器返回HTTP1.1头例如
HTTP/1.1 200 OK<cr><lf>
Server: Gnutella<cr><lf>
Content-type: application/binary<cr><lf>
Content-length: 4356789<cr><lf>
<cr><lf>
接下来是文件数据,应该读取服务器的HTTP响应中提供的Content-length定义的字符串长度。
注意:Servents应该使用HTTP1.1版本来完成文件传输,但也应该为HTTP1.0提供一些支持。Servents应该可以接收HTTP/1.0请求,而且应该用重新请求HTTP/1.0如果远端主机不遵从HTTP/1.1标准。
虽然我们强烈推荐有完全的HTTP1.1支持,一些serevents并没有做到这一点。但必须支持Gnutella的最重要特性,range requests和持续稳固的连接。但一些老servents,也没有做到这一噗。
Range requests在下面的窗口中
GET /get/2468/Foobar.mp3 HTTP/1.1<cr><lf>
User-Agent: Gnutella<cr><lf>
Host: 123.123.123.123:6346<cr><lf>
Connection: Keep-Alive<cr><lf>
Range: bytes=4932766-5066083<cr><lf>
<cr><lf>
注意Range头不一定要同时定义开始和结束位置。得到的响应如下
HTTP/1.1 206 Partial Content<cr><lf>
Server: Gnutella<cr><lf>
Content-Type: audio/mpeg<cr><lf>
Content-Length: 133318<cr><lf>
Content-Range: bytes 4932766-5066083/5332732<cr><lf>
<cr><lf>
Connection头告诉远端主机当传输完毕时是否应该关掉连接。"Connection: close"意思是当传完毕时必
须关掉连接。"Connection: Keep-Alive"或者没有Connection头意思是应该保持连接。客户端可能接着会
发出另一个范围或者另一个文件的请求。这个请求可能在上一次传输完成之前就已被发送。RFC2616的8.1
章节定义了持续连接。
必须忽视sevent无法识别的标题。
sevents不应该试图一次从同一个源下载多重的文件。文件应该被置为一个队列。
同样建议使用和理解在HUGE中描述的HTTP扩展。
防火墙后面的servents
在一次初始化文件下载的尝试中,建立一个到Guntella servent的直连并不总是成功的。这个servent可
能在一个防火墙后面,而这个防火墙并不允许到Guntella端口的incoming连接。如果无法建立一个直连,
尝试下载文件的servent可能会请求让共享文件的servent PUSH文件。servent可以通过把Push请求路由回
发送QueryHit消息的servent来请求一个文件PUSH。PUSH请求的目标servent(由PUSH消息的Servent
Identifier定义)应该,在收到PUSH消息之后,尝试建立一个新的TCP/IP连接到请求的servent(由PUSH消
息的IP地址和端口域定义)。如果这个无法建立这个直连,可能是因为发送PUSH请求的servent自己也位
于一个防火墙后面。这样的话,无法用这个文档描述的方法进行文件传输。
PUSH消息的用法
如果从一个不支持incoming连接的servent那里收到了一个QueryHit消息,这个servent可能会发送一个
PUSH消息。如果发送QueryHit消息的servent在防火墙后面,这就有可能发生。当servent收到一个Push消
息,当且仅当servent_Identifier域包含它本身的servent标识符时,它应该对PUSH请求发出响应。PUSH
消息头的Message_Id域不应该包含和它相关的QueryHit消息相同的值,而应该根据Message_Id生成算法生
成一个新的值。
PUSH消息通过Servent标识符的值被传会QueryHit的发起者。这意味者不同的PUSH消息可以有着同样的
Servent Identifier。如果头部的MessageID相同,PUSH消息应被认为是重复的。因为PUSH消息不被广播
,所以重复的消息应该是非常少的。
把文件“推送”到下载端
如果可以建立一个防火墙后的servent到发送PUSH消息的servent的直连,防火墙后的servent应该立即发
送下面的字符串
GIV <File Index>:<Servent Identifier>/<File Name><lf><lf>
<File Index>和<Servent Identifier>分别是收到的PUSH请求里面的File Index和Servent Identifier值,而<File Name>是本地文件表中文件索引号为<File Index>的文件的名称。文件可能是用URL/URI编码的。收到GIV的servent(想要收取文件的servent)应该忽略掉File Index和File Name,然后发起对要下载的文件的请求。发送GIV的servent必须允许客户端请求任何文件,而不只是PUSH消息中指定的那个文件。GET请求和 文件下载的其他过程和上述描述的方法相同。
Servent Identifier被格式化为十六进制格式,而且是大小写不相关的,例如
GIV 36:809BC12168A1852CFF5D7A785833F600/Foo.txt<lf><lf>
GIV 124:d51dff817f895598ff0065537c09d503/Bar.html<lf><lf>
如果在一个由PUSH发起的文件传输时TCP连接丢失,强烈建议发起TCP连接的servent(提供文件的servent
)尝试重新连接。这是很重要的,因为收取文件的servent可能没法再向提供文件的servent发送另一个PUSH消息。