Gnutella协议(2)核心协议

2 核心协议
2.1 启动
   在早期的Gnutella协议中,有少数几个固定地址的主机提供Gnutella主机列表,方便servent连接,这些主机叫做“主机cache”。但现在已经不用了。
   为了连接Gnutella网络,servent需要发现并存储多个主机的地址,有四种方法来得到这些主机地址:
    1、调用GWebCache
    2、在握手时(不管握手是否成功)存储从X-Try和X-Try-Ultrapeers头中读取的主机地址
    3、存储返回信息中的主机地址(在至少一个连接建立以后)
    4、存储从QueryHit消息中读取的主机地址(在至少建立两个连接以后),这种情况假设远端的servent对上传部分和GNet部分使用相同的端口(大多数情况都是这样)
   GWebCache (GWC)协议允许通过向GWebCache服务器(参见3.2节 WebCache 系统 )发送一个HTTP请求来得到主机地址,但是,应该非常小心不要向web cache发送太多的请求,一般情况下,在一个会话期内,查询GWebCache的次数不应该超过一次。这就意味着,如果一个用户掉线需要重新连接时,servent不应该查询GWebCache,作为替代,servent应该实现本机cache(参见 3.1节 本地主机cache)来存储以上四种方式发现的主机地址,并和GWebCache一起使用。
    以下是推荐的启动算法,应该能够保证每个session调用GWebCache 的次数接近1(稍微大一点)
   * 启动客户端并调入本地主机cache
   * 使用本地主机cache连接GNet.
   * 如果在X秒后还没有连接到GNet,查询一个随机的GWebCache.
   * 等待最少Y秒后,向另一个GWebCache发送请求。(在第一次选取的GWebCache宕机或反应非常慢的情况下).
   * 在Y秒后,如果第一次GWC调用还没有返回结果,每隔Z秒调用一个新的GWC,直到收到其中一个的响应。
   * 一旦至少收到一个GWC的返回结果,servent就不应该在接下来的session中发送新的请求,除非本地主机cache为空(如果软件提供初始的主机cache,并且没有清除主机cache的选项,这种情况就不应该发生).
    假设X,Y和Z的值分别为5秒、10秒和2秒,这样应该能够确保servent能够很快连接到GNet并把查询GWebCache的次数控制到最低。
启动过程还应该确保servent不用经常连接一个远程主机,每秒钟少于一次是一个合理的界限。为了达到这个要求,一个解决办法就是确保本地主机cache中总是有足够多的主机,这样就能保证任何一个主机都不会在一分钟之内被再次试图连接。
    每一个servent在握手时都应该发送一个X-Try头,这个头给出这个servent可以尝试连接的主机列表,允许它在不用调用GWebCache的情况下得到新的主机地址。例如:
   X-Try:  1.2.3.4:1234, 1.2.3.5:6346, 1.2.3.6:6347
    servent应该发送适当数量的主机,一般在10到20之间。这些随着X-Try头发送的主机信息应该是确切的。所以,servent应该只选择那些最近(在发送X-Try头之前几分钟内)所知还在运行的主机。如果最近发现还在运行的主机数量少于10个,那发送时就少发送一些。如果servent实现了响应cache(参见3.4节),在响应信息中发现的主机可以认为具有空闲的时隙。但这不适用于在监控QueryHits中得到的主机信息。这样,在返回信息中得到的主机信息可以替换X-Try头中主机的位置,而QueryHits中发现的主机却不行。

2.2 握手协议
来源 : 初始化握手建议
    一个Gnutella servent通过与当前在网络上的另一个servent建立连接的方式把自己连接到Gnutella网络。发现第一个在网上的主机的技术已经在2.1节描述过了。一旦第一个连接建立起来,网络可以提供更多的当前连接在网上上的主机地址。Gnutella的缺省端口是6346,但servent可以使用其他任何当前没有使用的端口。如果这个端口(指6346端口)正在使用(可能由另一个Gnutella servent应用占用),servent应该尝试监听其他端口,正在监听的端口的信息可以通过应答信息在网络中进行广告。
    关于如何选择连接其他Gnutella主机、何时接受连接请求的规则和技术参见附件4
    一旦得到当前在网上的另一个servent的地址,首先建立一个到servent的TCP/IP连接,然后开始一个握手的消息序列。客户端是发起连接的主机,服务器是接受连接的主机。<cr>代表ASCII码的13(回车),<lf>代表10(换行)。
    1.客户端建立一个到服务器的TCP连接
    2.客户端发送“GNUTELLA CONNECT/0.6<cr><lf>”
    3.客户端发送带有所有域的头,除了供应商自定义的特殊域,每个域以"<cr><lf>"结束,在整个头的最后添加一个额外的"<cr><lf>"
    4.服务器以"GNUTELLA/0.6 200 <string><cr><lf>"应答,这里<string>应该是"ok",但是servent应该只查找"200"这个代码作为状态标志。
    5.服务器发送自己信息头的全部,按照3规定的格式
    6.如果客户端在解析完服务器发送的信息头以后,还希望继续,就发送"GNUTELLA/0.6 200 OK <cr><lf>"给服务器作为应答,就象4一样。否则,它需要返回一个错误码,并关闭这个连接。
    7.客户端发送供应商自定义的信息头,与3的格式一样。
    8.客户端和服务器按照3和5中得到的信息,按照自己的要求发送和接收二进制消息。
    所有的信息头应该在http://groups.yahoo.com/group/the_gdf/database?method=reportRows&tbl=9的GDF数据库中注册(要求是GDF的成员)
    信息头遵循RFC822和RFC2616标准。每个域由字段名称和字段值组成,中间用冒号隔开。每行以<cr><lf>结束,整个信息头以一个单独的<cr><lf>行结束。正常情况下每行起始都是一个域,也可以是空格或水平制表符(分别是ASCII码的32和9),后面紧接着信息头的域。多余的空格和制表符可以把多行的头域的值压缩到一个空间中,例如:
   First-Field: this is the value of the first field<cr><lf>
    Second-Field: this is the value<cr><lf>
        of the<cr><lf>
        second field<cr><lf>
    <cr><lf>
上面的信息头由两个域组成,"First-Field" 和 "Second-Field",它们的值分别是“this is the value of the first   
field”和“this is the value of the second field”(前面起连接作用的空格被忽略了)。注意,在":"之后,在字段名结束之后,在字段值开始之前的前导空格并不被忽略。
    多个具有相同字段名的头域行与一个头域行,其字段名之间与","隔开是等价的。只意味着:
    Field: first<cr><lf>
    Field: second<cr><lf>
    与Field: first,second<cr><lf>具有完全相同的含义,也就是说,在这种情况下,各行之间的顺序是严格要求的。
    下面是一个客户端和服务器交互的例子。从客户端发送到服务器的数据列在左边,从服务器发送到客户端的数据列在右边。
     Client                          Server
       -----------------------------------------------------------
       GNUTELLA CONNECT/0.6<cr><lf>
       User-Agent: BearShare/1.0<cr><lf>
       Pong-Caching: 0.1<cr><lf>
       GGEP: 0.5<cr><lf>
       <cr><lf>
                                       GNUTELLA/0.6 200 OK<cr><lf>
                                       User-Agent: BearShare/1.0<cr><lf>
                                       Pong-Caching: 0.1<cr><lf>
                                       GGEP: 0.5<cr><lf>
                                       Private-Data: 5ef89a<cr><lf>
                                       <cr><lf>
       GNUTELLA/0.6 200 OK<cr><lf>
       Private-Data: a04fce<cr><lf>
       <cr><lf>
      [binary messages]                [binary messages]

    一些需要注意的地方:首先,客户端(或服务器)在第4步收到除了"200"代码以外的其它响应时,应该断开连接,当前没有必要定义这些错误代码。第二,servent应该忽略第2步、第4步和第6步可能出现的高版本。例如,将来可能出现一个客户端发出"GNUTELLA CONNECT/0.7"来连接服务器,这是完全可以接受的,服务器如果支持0.7版的协议,应该返回"GNUTELLA/0.7 200 OK",否则返回"GNUTELLA/0.6 200 OK"。
   关于信息头一些应该注意的地方:servent应该在任何时候使用标准的HTTP头,例如,servent应该使用标准的"User-Agent",而不是创造一个"Servent-Vendor"。但是,如果没有合适的HTTP头可以使用,完全可以添加新的头(如:"Query-Routing"),只要这些新的头符合HTTP的语法就行。servent不认识的头必须被忽略。一些旧的servent会发送"GNUTELLA CONNECT/0.4<lf><lf>"来初始化一个握手过程,服务器如果可以接受这个连接请求,应该以"GNUTELLA OK<lf><lf>"应答,后面跟随二进制消息。servent在以0.6协议连接被拒绝后,可能使用0.4协议尝试连接。在0.4协议中,不使用任何握手信息头。
    servent拒绝一个连接时,如果可能的话,必须向要求连接的远程主机提供一个其它Gnutella主机的列表,这样,远程主机可以尝试连接这些主机。这个列表应该使用X-Try头来提供,一个X-Try头与以下类似:
    X-Try:1.2.3.4:1234,3.4.5.6:3456
    在","和"."之后,可能会有一个空格。在一个信息头集中,可能包含多个X-Try头。整个信息头可能以一个额外的"."结束。整个信息头可能通过连接符格式化在多行中。每个X-Try头给出一个servent的IP地址和监听端口。有时候这被称作“connection pong”。如果发送X-Try的服务器实现了响应cache机制,每个被发送的连接响应必须是最新的。
    拒绝一个连接的一般原因是该servent忙,代码"503",后面紧跟着“busy”或其它描述信息。

2.3 叶子模式和超级节点模式
来源 - Ultrapeer Proposal v1.0.

最初,所有的Gnutella节点随机地连接在一起,这样的网络对宽带用户工作地很好,但对于使用很慢的猫的用户来说就不行了。我们可以通过对网络进行组织来解决这个问题。

2.3.1 超级节点系统

        超级节点系统对解决这个问题是很有效的。这种系统是通过把节点分为叶子节点和超级节点从而使整个网络形成一种层次结构,一个叶子节点只维持少量对超级节点的连接。一个超级节点作为连接到自己的叶子节点的代理服务器。这对提高Gnutella网络的灵活性非常有好处,降低了在消息处理和路由过程中涉及的节点数量,同时也降低了节点之间的实际流量。
      超级节点只有在认为叶节点能够应答的情况下,才会向该节点转发查询请求,而叶节点从来不会向超级节点转发请求。超级节点相互连接在一起,并和“一般的”Gnutella主机(没有实现超级节点系统的主机)连接在一起。
      超级节点使用查询路由协议(Query Routing Protocol, QRP参见3.8)决定把什么样的查询发送给叶节点,如果超级节点和叶节点都支持其他协议,也可以使用该协议决定转发哪些查询。QRP协议不用在超级节点/普通节点之间。
     推荐servent实现超级节点系统,或将来发展的其他类似系统,来降低占用modem用户带宽的数量。
     超级节点选举是自组织的,选择叶节点模式和超级节点模式的规则细节可以在3.7 超级节点选举法则中找到。
    更多的细节请参考最初协议规定。
2.3.2 超级节点握手
    超级节点的容量和信息在试图建立新的Gnutella连接时,通过握手序列(参见2.2)进行交换,使用下面新的信息头:
     * X-Ultrapeer: "True" 通知该节点是一个超级节点,"False"通知该节点是一个受保护的叶节点。
     * X-Ultrapeer-Needed: 用来平衡超级节点的数量
     * X-Try-Ultrapeers: 与X-Try(参见2.1)相似,但只包含超级节点的地址。
     * X-Query-Routing: 表示支持查询路由协议(参见3.8)。该头域的值应该是QRP的版本号(当前是0.1)
    特别注意,这些信息头域可以以任何顺序发送,"True","false"是大小写无关的。
    下面是一个叶节点连接超级节点的交互的例子:

   叶节点                            超级节点
  -----------------------------------------------------------
   GNUTELLA CONNECT/0.6
   User-Agent: LimeWire/1.0
   X-Ultrapeer: False
   X-Query-Routing: 0.1
  
                                    GNUTELLA/0.6 200 OK
                                    User-Agent: LimeWire/1.0
                                    X-Ultrapeer: True
                                    X-Ultrapeer-Needed: False
                                    X-Query-Routing: 0.1
                                    X-Try: 24.37.144:6346,
                                     193.205.63.22:6346
                                    X-Try-Ultrapeers: 23.35.1.7:6346,
                                     18.207.63.25:6347
                                   
   GNUTELLA/0.6 200 OK
  

   [二进制消息]                [二进制消息]

现在叶节点在超级节点的掩盖之下了,也节点应该断掉所有的与不是超级节点的连接并发送一个QRP路由表(假设使用QRP)。如果一个叶节点接收到连接请求,它应该返回一个503的错误码,拒绝接受这个连接,同时发送X-Try和X-Try-Ultrapeer头把远程主机转到其他地址。例如,当一个叶节点试图连接另一个叶节点时,应该看起来像下面这样,不重要de信息头在这个和以后的例子中都被去掉了。 

   叶节点1                            叶节点2
   -----------------------------------------------------------
   GNUTELLA CONNECT/0.6
   X-Ultrapeer: False
  
                                    GNUTELLA/0.6 503 I am a leaf
                                    X-Ultrapeer: False
                                    X-Try: 24.37.144:6346
                                    X-Try-Ultrapeers: 23.35.1.7:6346
                                   

                                    [中断连接]


   有时一些节点无法作为超级节点,但又不能找到超级节点。在这种情况下,他们就像老的,没有路由功能的Gnutella 0.4连接一样工作。
   叶节点1                            叶节点2
   -----------------------------------------------------------
   GNUTELLA CONNECT/0.6
   X-Ultrapeer: False
  
                                    GNUTELLA/0.6 200 OK
                                    X-Ultrapeer: False
                                   
   GNUTELLA/0.6 200 OK
  

   [二进制消息]                      [二进制消息]


     当两个超级节点相遇,两个都设X-Ultrapeer: true。如果两个超级节点都有叶节点,他们在交互结束后仍然是超级节点。注意这种情况下,在连接建立后相互之间并不发送路由表,下面是握手的例子:
   UltrapeerA                       UltrapeerB
   -----------------------------------------------------------
   GNUTELLA CONNECT/0.6
   X-Ultrapeer: True
  
                                    GNUTELLA/0.6 200 OK
                                    X-Ultrapeer: True
                                   

   GNUTELLA/0.6 200 OK
  

   [二进制消息]                [二进制消息]


有时有太多的可以成为超级节点的节点。考虑一个超级节点A正在连接超级节点B,如果B并没有很多节点,它可能指示A变成一个叶节点。如果A没有叶节点连接,它停止接收新的连接,断掉任何Gnutella 0.4的连接后,发送一个QFP路由表给B。然后B屏蔽A的所有流量。如果A有叶节点的连接,它忽略B的指示,就像上面的例子一样工作。
   超级节点A                         超级节点B
   -----------------------------------------------------------
   GNUTELLA CONNECT/0.6
   X-Ultrapeer: True
  
                                    GNUTELLA/0.6 200 OK
                                    X-Ultrapeer: True
                                    X-Ultrapeer-Needed: False
                                   

   GNUTELLA/0.6 200 OK
   X-Ultrapeer: False
  

   [二进制消息]                [二进制消息]


2.5 交换网络拓扑信息

2.5.1 使用Ping和Pong信息

        在早期版本的Gnutella中,ping消息在网络中广播,pong消息通过与查询命中消息相同的方式路由到ping消息的发起者(参见2.7节)。这种做法要求网络有足够的带宽,所以现在的Gnutella servent缓存pong消息,或使用其他方式尽量减少ping和pong消息所占用的带宽。这样,pong消息就不能准确反映网络的拓扑结构,另外,没有可用GNet连接的servent根本不对ping消息发出应答。所以,pong消息在将来可能被更有效率的通信方式取代。截至本文发表时,仍然没有发现Gnutella网络拓扑结构的替代方案。


2.7 查询

本章描述查询和查询命中消息的使用。

2.7.1 转发、路由查询和查询命中消息
    Servent应该向所有与自己直接连接的servent转发接收到的查询消息,传入这个消息的servent除外。使用Flow控制和超级节点(3.1节和3.2节)的servent并不总是转发所有的消息到所有的连接。
    servent在把一个消息转发给任何直接连接的servent之前,必须递减消息头中的TTL域,递增Hops域。如果在递减TTL域后,发现其值为0,这条消息就不能转发到任何连接。
    servent在接收到与以前接收到的某个消息具有相同的内容和消息ID的消息时,必须丢弃该消息,因为这意味这该消息已经被处理了。
    查询命中消息只能沿着查询消息进入的路径发送,这样能够保证只有那些曾经路由过该查询消息的servent才能收到查询的应答。如果一个servent收到一个消息ID=n的查询命中消息,但并没有收到过消息ID=n的查询消息,它应该把该查询命中消息从网络上去掉。

2.7.2 何时和如何发送新的查询消息
    查询消息通常在用户发起一个搜索时发出,一个servent也可能自动产生查询消息,例如在查找一个资源的更多位置时。如果这样做,servent必须小心不能阻塞网络。一个servent在一小时内不应该发送一条以上的自动查询消息。
    servent不应该允许用户通过重复点击按钮来产生大量的查询。
    servent应该查看与它相临的servent发起的查询(Hops=0),如果这些查询过于频繁,是重复发送的消息或其他方式的非正常操作,就应该丢弃这些查询甚至关闭与发送他们的servent的连接。
    一个新的查询的TTL值不应该大于7,绝对不能大于10。Hops值必须设为0。

2.7.3 何时和如何用查询命中消息应答
    当一个servent接收到查询消息,它应该按照查询标准搜索本地共享的文件。搜索条件是文本的,从来没有定义这些文本应该用什么字符集,所以,servent必须假设他们是纯ASCII的。如果有字节的第7位(最高位)不是0,那么要么是一个GGEP扩展,指定了使用的字符集,要么就由servent来猜出正确的字符集,一般情况下,字符集是ISO-latin-1或UTF-8
    如何处理搜索条件也没有准确规定,但是有一些在servent之间互操作的指南:
    搜索条件是一个关键字组成的字符串,servent应该只选择具有全部关键字的文件作为应答;推荐在非数字文字字符(即不是字母也不是数字的字符)处分词;空格是关键词之间的标准分隔符。
    servent可能要求只有与搜索条件中关键字出现相同次数和相同顺序的才是满足条件的条目。
    不支持正则表达式,一般的通配符如"*"和".",要不代表它们自己要不被忽略,查询匹配时应该是大小写不敏感的,空查询或只包含一个字符的查询应该被忽略。
    GGEP扩展可以用来提供如何解析搜索条件的细节(例如指定可以使用正则表达式进行匹配),但是一个servent无法确定其他servent是否理解GGEP扩展。
    为了避免过于广泛的查询,servent可能忽略那些搜索条件短于一定长度的查询。
    TTL=1、Hops=0,搜索条件等于“    ”(四个空格)的查询消息用来列出一个主机共享的所有文件,servent应该用自己共享的所有文件来应答,如果共享的文件很多,应该使用多个查询命中消息。如果因为隐私或带宽限制,可以不应答这种列表查询请求。
    查询命中消息必须具有与查询消息相同的消息ID,TTL值最少应该设为对应查询消息的Hops值加2,这样如果需要的话,就允许查询命中消息使用一个相对较长的路径返回。TTL值最少必须设置为对应查询的Hops值,查询命中消息的Hops值的初值必须设为0。有些servent使用(2 * Query_TTL + 2)的TTL值,以保证应答能够到达目的地。具有高的TTL值的应答应该被允许通过。
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值