一个Gnutella客户机通过与另一个当前在网络中的客户机建立连接来使自己与网络相连。
一旦网络上的另一个客户机的地址被获取,一个与该客户机的TCP/IP连接将被创建,以下的Gnutella连接请求字符串(ASCII编码)将被发送:
GNUTELLA CONNECT/<protocol version string>
Peercast定义:static const char *GNU_CONNECT = "GNUTELLA CONNECT/0.6";
一个客户机愿意接受连接的话必须回应
GNUTELLA OK
Peercast定义:static const char *GNU_OK = "GNUTELLA/0.6 200 OK";
一旦一个客户机成功连接到网络上,他与其它客户机通讯通过发送和接收Gnutella协议描述字。每一个描述符前都有一个以下字节结构的描述头,如下所示:
Descriptor Header
DescriptorID网络描述符:16个字节的字符串唯一标示网络的描述符号。
Payload Descriptor负载描述符:
0x00 = Ping
0x01 = Pong
0x40 = Push
0x80 = Query
0x81 = QueryHit
Peercast定义:
static const int GNU_FUNC_PING = 0;
static const int GNU_FUNC_PONG = 1;
static const int GNU_FUNC_QUERY = 128;
static const int GNU_FUNC_HIT = 129;
static const int GNU_FUNC_PUSH = 64;
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区的有效性(至少为固定长度的描述符)。如果一个客户机不能和输入的流同步,它应该断掉与这个输入流有关的来自发送方的客户机,不管是产生这个流还是向前传递这个流的无效的客户机。紧接着描述头的是一个有效装载包含以下之一的描述符:
Peercast定义一个GnuPacket类来保存包的信息。
class GnuPacket
{
public:
unsigned char func; //描述符类型,包括Ping/Pong/Query/Hit/Push
unsigned char ttl; //生存周期
unsigned char hops; //记录描述符被传送的次数
unsigned int len; //数据长度
GnuID id; //描述符ID:由16字节组成,用于唯一标识一个网络描述符
char data[MAX_DATA]; //实际数据
};
Ping (0x00)
Ping描述符没有相关的有效装载和数据长度为0。一个Ping只是简单地有一个描述头表述,它的有效装载区是0x00和装载长度区为0x00000000。
一个客户机用Ping描述符
Peercast实现:void GnuPacket::initPing(int t)
只需简单设置ping描述头表述和TTL值(初始化为t)及hop值(初始化为0),并生成校验ID
Pong (0x01)
Port
Port:同意接收响应的客户机的端口
IP Address:响应的客户机的地址(此数据区高位字节在后)
Number of Files Shared:本机共享文件的数量
Number of Kilobytes Shared:本机所有共享文件的空间大小,以K为单位
Peercast实现:void GnuPacket::initPong(Host &h, bool ownPong, GnuPacket &ping)
data.writeShort(h.port); // 写入响应的客户机端口
data.writeLong(SWAP4(h.ip)); // 响应的客户机的地址
data.writeLong(chanMgr->numChannels()); //本机频道的数量
data.writeLong(servMgr->totalOutput(false)); // 本机总输出数据量大小,以K为单位
Query (0x80)
字节偏移0 1 2 …
Minimum Speed :最小响应速度,响应的客户机的速度必须在此速度之上( 以K/秒为单位)
Search criteria:查询关键字,一个零结尾的字符串。这个字符串的最大长度由描述头的Payload Length负载长度规定。
Peercast实现:void GnuPacket::initFind(const char *str, XML *xml, int maxTTL)
mem.writeShort(0); // 最小响应速度为0
mem.write((void *)str,slen+1); // 写入要搜索的字符串
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指令进行响应。
Peercast定义:bool GnuPacket::initHit(Host &h, Channel *ch, GnuPacket *query, bool push, bool busy, bool stable, bool tracker, int maxttl)
mem.writeChar(1); // 能接受连接的客户机的端口
mem.writeShort(h.port); // 能接受连接的客户机的端口
mem.writeLong(SWAP4(h.ip)); // 响应客户机的地址(此数据区高位字节在后)
mem.writeLong(0); // index
mem.writeShort(ch->getBitrate()); // 响应客户机的连线速度(以K/秒为单位)
mem.writeShort(ch->localListeners()); // 听众数目
Push (0x40)
Servent Identifier:一个16位的字符串用来唯一标示网络上的客户机,该客户机请求下载带有File_Index的文件。
File Index:下载目标客户机的文件的唯一标识,初始化的客户机应该根据返回的QueryHit指令的File_Index中的标识设置。
IP Address:下载带有File_Index的文件的客户机的地址(此数据区高位字节在后)
Port:下载带有File_Index的文件的客户机的端口
Peercast定义:void GnuPacket::initPush(ChanHit &ch, Host &sh)
data.write(ch.packetID.id,16); //一个16位的字符串用来唯一标示网络上的客户机,该客户机请求下载带有Channel_Index的文件。
data.writeLong(ch.index); //下载目标客户机的频道的唯一标识
data.writeLong(SWAP4(sh.ip)); // 下载带有Channel_Index的频道的客户机的地址(此数据区高位字节在后)
data.writeShort(sh.port); // 下载带有Channel_Index的频道的客户机的端口