eDonkey协议

1.简介
ED2K 协议 用于对等的客户端之间的通讯,众多对等的客户端形成了基于服务器的P2P文件共享网络。
下面几个词语用于包格式字符串:
u8      unsigned 8-bit integer
u16     unsigned 16-bit integer
u32     unsigned 32-bit integer
hash    16-bytes md4 checksum
string  16-bit integer (specifying string length) followed by the string
Taglist 32-bit integer (specifying number of tags) followed by the tags
一些定义:
part:
最大9500kb的文件片断,每个片断都有对应的MD4 PartHash.
chunk:
最大180kb的文件片断,每个片断都可以被远程的客户端请求
block:
chunk片断,每个片断被封装成单独的数据包在客户端之间传输。

2.Header 规格
 +--------+--------+--------+--------+--------+--------+--------+--------+----
 |protocol|            packet length          |  packet data (length bytes)
 +--------+--------+--------+--------+--------+--------+--------+--------+----
每个ED2K包协议以协议代码开始,后面跟着的是包的长度(32-bit unsigned的整数)。
现在的协议代码有下面三个:
 PR_ED2K  = 0xe3,  //!< Standard, historical eDonkey2000 protocol
 PR_EMULE = 0xc5,  //!< eMule extended protocol
 PR_ZLIB  = 0xd4   //!< Packet payload is compressed with gzip
包数据通常以8-bit的代码开始,用于指示包的内容,如果以PR_ZLIB协议发送包,
包的数据经过压缩的。

3.客户端-服务端通讯
3.1 登陆服务器
客户端如果要连接到eDonkey2000服务器,发送OP_LOGINREQUEST给服务器.
 OP_LOGINREQUEST = 0x01, //!< <hash>hash<u32>ip<u16>port<TagList>tags
这个包可能包括下面的标签(tags):
        CT_NICK         = 0x01, //!< <string>nick
        CT_VERSION      = 0x11, //!< <u8>0x3c
        CT_PORT         = 0x0f, //!< <u16>port
        CT_MULEVERSION  = 0xfb, //!< <u32>ver
        CT_FLAGS        = 0x20, //!< <u8>flags
flags可能包含下面信息:
        FL_ZLIB         = 0x01, //!< zlib compression support
        FL_IPINLOGIN    = 0x02, //!< Client sends its own ip during login
        FL_AUXPORT      = 0x04, //!< ???
        FL_NEWTAGS      = 0x08, //!< support for new-styled eMule tags
        FL_UNICODE      = 0x10  //!< support for unicode
对一个良好格式的OP_LOGINREQUEST包,服务器先以这个包报告的监听端口尝试连接到发送这个数据包的客户端,
之后如果服务器收到一个OP_HELLOANSWER包,说明连接成功,那么服务为该客户端分配一个高ID,否则可能是因为某些原因连接失败,那么为该客户端分配一个低ID。
服务器发送下面的数据包通知客户端连接已经建立,同时也提供了和服务器有关的更新信息。
        OP_SERVERMESSAGE = 0x38, //!< <u16>len<len>message
        OP_SERVERSTATUS  = 0x34, //!< <u32>users<u32>files
        OP_IDCHANGE      = 0x40  //!< <u32>newid
注意:Lugdunum 16.44+服务器(什么来的?)发送一个附加的u32的ID_CHANGE数据包,包含了一些支持特性,包括:
        FL_ZLIB         = 0x01, //!< zlib compression support
        FL_NEWTAGS      = 0x08, //!< support for new-styled eMule tags
        FL_UNICODE      = 0x10  //!< support for unicode
3.2 更新服务器列表和信息
和服务器建立连接之后,客户端可以象服务器请求其他信息,象服务器列表:
        OP_GETSERVERLIST = 0x14, //!< (no payload)
服务器用下面的包回应:
        OP_SERVERLIST    = 0x32, //!< <u8>count[{<u32>ip<u16>port}*count]
        OP_SERVERIDENT   = 0x41, //!< <hash>hash<u32>ip<u16>port<TagList>tags
后面包含着下面的tags:
        CT_SERVERNAME    = 0x01, //!< <string>name
        CT_SERVERDESC    = 0x0b  //!< <string>desc
3.2 发布共享文件
和服务器建立连接之后,客户端必须用OP_OFFERFILES包发布共享文件,如果服务器支持的话这些包必须被压缩以节省带宽。
        //! <u32>count[<count>*{<hash>filehash<u32>ip<u16>port<TagList>tags}]
        OP_OFFERFILES    = 0x15,
这个包可能包含下面的tags:
        CT_FILENAME      = 0x01,       //!< <string>name
        CT_FILESIZE      = 0x02,       //!< <u32>size
        CT_FILETYPE      = 0x03,       //!< <string>type
注意:
文件类型用字符串发送,下面的字符串会被识别:
 #define FT_ED2K_AUDIO    "Audio"    //!< mp3/ogg/wma      etc
 #define FT_ED2K_VIDEO    "Video"    //!< avi/mpg/mpeg/wmv etc
 #define FT_ED2K_IMAGE    "Image"    //!< png/jpg/gif/tiff etc
 #define FT_ED2K_DOCUMENT "Doc"      //!< txt/doc/rtf      etc
 #define FT_ED2K_PROGRAM  "Pro"      //!< exe/bin/cue/iso  etc
这个包在下面时机会被发送:
 .拥有一个共享文件列表,并连接到服务器的时候。
 .添加新的共享文件的时候。
 .As empty packet, as server keep-alive packet at regular intervals  Additionally,可以用两对特殊的IP/PORT    值来指示共享的文件是部分的还是完整的。
         FL_COMPLETE_ID   = 0xfcfcfcfc, //!< File is complete - send this as ID
         FL_COMPLETE_PORT = 0xfcfc,     //!< File is complete - send this as port
         FL_PARTIAL_ID    = 0xfbfbfbfb, //!< File is partial  - send this as ID
         FL_PARTIAL_PORT  = 0xfbfb      //!< File is partial  - send this as port
3.3 搜索
搜索在eDonkey2000网络是基于服务器的。客户端发送SEARCHR包给服务器,接着服务器返回一个或多个SEARCHRESULT包。这些都只是和当前连接的服务器的TCP层进行。但是,客户端也可以用GLOBSEARCH代码,通过UDP发送相同的请求包给所有已经的服务器,之后服务器也会用 GLOBSEARCHRES包回应(也是UDP)。
        OP_SEARCH        = 0x16,       //!< <searchexpr>
        //! <u32>count[<count>*{<Hash>hash<u32>id<u16>port<Taglist>tags}]
        OP_SEARCHRESULT  = 0x33,
搜索结果可能包含下面的tags:
        CT_FILENAME      = 0x01,       //!< <string>name
        CT_FILESIZE      = 0x02,       //!< <u32>size
        CT_FILETYPE      = 0x03,       //!< <string>type
        CT_SOURCES       = 0x15,       //!< <u32>numsrc
        CT_COMPLSRC      = 0x30,       //!< <u32>numcomplsrc

4.客户端 - 客户端通信
4.1 get-to-know-you chit-chat
为了初始化和远程客户端的连接,客户端先要发送 Hello 包,
        OP_HELLO        = 0x01, //!< <hash>hash<u32>ip<u16>port<TagList>tags
Hello 可以 包含下面的 tags:
        CT_NICK         = 0x01, //!< <string>nick
        CT_PORT         = 0x0f, //!< <u16>port
        CT_MULEVERSION  = 0xfb, //!< <u32>ver
        CT_MODSTR       = 0x55, //!< <string>modstring
        CT_UDPPORTS     = 0xf9, //!< <u16>kadudpport<u16>ed2kudpport
        CT_MISCFEATURES = 0xfa, //!< <u32>features bitset
对OP_HELLO正确的回应是OP_HELLOANSWER。这个HelloAnswer包的格式和OP_HELLO是一样的,只是HelloAnswer还包括了8-bit的hash长度。这个域的值必须是0x0f。
CT_MULEVERSION是一个32-bit的值,下面是bits的意义:
 1       2       3       4       5 [bytes]
 11111111222222233333334445555555  [bits]
 00000000000000001010101100000000  eMule 42g version info
     |      |      |    |    +---- Build version        (unused by eMule)
     |      |      |    +--------- Update version 6     (a = 0, g = 6)
     |      |      +-------------- Minor version 42
     |      +--------------------- Major version        (unused by eMule)
     +---------------------------- Compatible Client ID (CS_EMULE = 0x00)
如果两个客户端是eMule-compatible兼容的,那么他们必须交换MuleInfo包:
        OP_MULEINFO       = 0x01, //!< <u8>clientver<u8>protver<TagList>tags
        OP_MULEINFOANSWER = 0x02  //!< <u8>clientver<u8>protver<TagList>tags
注意:所有这两个包都是用PR_EMULE来发送的。
在MuleInfo包里面的tags有:
        CT_COMPRESSION    = 0x20, //!< u32 compression version
        CT_UDPPORT        = 0x21, //!< u32 udp port
        CT_UDPVER         = 0x22, //!< u32 udp protocol version
        CT_SOURCEEXCH     = 0x23, //!< u32 source exchange version
        CT_COMMENTS       = 0x24, //!< u32 comment version
        CT_EXTREQ         = 0x25, //!< u32 extended request version
        CT_COMPATCLIENT   = 0x26, //!< u32 compatible client ID
        CT_FEATURES       = 0x27, //!< u32 supported features bitset
        CT_MODVERSION     = 0x55, //!< <string>modversion (may also be int)
        CT_MODPLUS        = 0x99, //!< mh? (Source: eMule+ Forums ... )
        CT_L2HAC          = 0x3e, //!< mh? (Source: eMule+ Forums ... )
Feature 集标志为 是32-bit 值,意义如下:
 12345678123456781234567812345678
 00000100000100110011001000011110 eMule 43b
 00110100000100110011001000011110 eMule 44b
 11123333444455556666777788889abc
  | |  |   |  |   |    |  |  |||+-- Preview
  | |  |   |  |   |    |  |  ||+--- Multipacket
  | |  |   |  |   |    |  |  |+---- No `view shared files' supported
  | |  |   |  |   |    |  |  +----- Peercache
  | |  |   |  |   |    |  +-------- Comments
  | |  |   |  |   |    +----------- Extended requests
  | |  |   |  |   +---------------- Source exchange
  | |  |   |  +-------------------- Secure ident
  | |  |   +----------------------- Data compression version
  | |  +--------------------------- UDP version
  | +------------------------------ Unicode
  +-------------------------------- AICH version (0 - not supported)
4.2 请求一个文件
在完成入 4.1所描述的握手之后,客户端现在可以请求一个文件了。这个由ReqFile包来完成。
        OP_REQFILE         = 0x58, //!< <hash>hash
OP_FILENAME和 OP_FILEDESC回应ReqFile
        OP_FILENAME        = 0x59, //!< <hash>hash<u32>len<len>name
        OP_FILEDESC        = 0x61, //!< <u8>rating<u32>len<len>comment
接收了上面的数据包之后,下载方客户端发送SETREQFILEID 把刚才请求的文件绑定到一个hash上,这意味着现在客户端被绑定到被请求的hash上,直到它接受到它所请求的东西。
FileDesc用 PR_EMULE发送,但并不包含描述所属的文件的hash,然而,由于它经常跟着OP_FILENAME发送,所以客户端可以假定FileDesc属于最近所请求的那个文件。
发送这个包之后客户端会期望得到回应是OP_FILESTATUS
        OP_REQFILE_STATUS  = 0x50, //!< <hash>hash<u16>count<count>partmap
如果上传方客户端认识到它并没有共享被要求上传的文件,那么它会发送OP_REQFILE_NOFILE。File status包含了有效parts的partmap.这被定义为为一个bitfield,每个bit代表了一个ED2K part(由ED2K_PARTSIZE定义).多处理的bit被0填充。
先于bitset的count指示了总的parts的数量。注意sending partmap完全是可选,并且只有当然共享文件是部分的才可以发送,如果共享文件是完整的,那么partmap将被忽略.
        OP_REQFILE_NOFILE  = 0x48, //!< <hash>hash
如果下载方客户端需要一个文件的hashset,它可以通过发送OP_REQHASHSET向上传方请求。期望得到的回应是OP_HASHSET
        OP_REQHASHSET      = 0x51, //!< <hash>hash
        OP_HASHSET         = 0x52, //!< <hash>hash<u16>cnt[cnt*<hash>parthash]
在这之后,请求方发送OP_STARTUPLOADREQ。被请求方要么用OP_ACCEPTUPLOADREQ回应(如果我们能开始正确的上传),要么用OP_QUEUERANKING回应,说明请求方的请求已经被插入到上传队列里了。
        OP_STARTUPLOADREQ  = 0x54, //!< may contain <hash>hash (emule)
        OP_ACCEPTUPLOADREQ = 0x55, //!< Empty
        OP_QUEUERANKING    = 0x5c, //!< <u32>queueranking
4.3 上传文件数据
接收到OP_ACCEPTUPLOADREQ之后,下载方客户端将会继续请求chunks。在 eDonkey2000 网络,一个chunck的大小是180k。用OP_REQCHUNKS包请求chunks。
        OP_REQCHUNKS      = 0x47, //!< <hash>hash[3*<u32>begin][3*<u32>end]
这个包包含了被请求的chunks的3个开始偏移和3个结束偏移。ED2K 数据 将结束偏移定义为是唯一的(exclusive),因此象(0, 0)这样的范围被认为是无效的范围。如果下载方客户断请求少于3个chunks,那么这个包可以包含象(0,0)的保留范围来指示。
接收到OP_REQCHUNKS后,上传方客户断开始发送数据。被请求的chunks 被分成最大10kb的blocks,每个blocks用OP_SENDINGCHUNK作为单独的数据包传送,
        OP_SENDINGCHUNK   = 0x46, //!< <hash>hash<u32>begin<u32>end<data>
当下载方客户端决定不再接收任何数据的时候,它可以发送OP_CANCELTRANSFER包给上传方客户端。
        OP_CANCELTRANSFER = 0x56  //!< empty
5 低ID客户端
低ID客户端在ed2k网络被定义这样的客户端:
无法接收外部连接的客户断。这样的客户端只能自己主动创建外部连接。连接低ID客户端唯一的方法就是通过服务器的回调,只要这个客户端已经连接到服务器。只有连接到同样的服务器的客户端才能连接到低ID客户端。低ID客户端在网络中被分配小于0x00ffffff的ID。这也是很多地方我们把IP称为ID的原因,因为如果客户端的ID大于0x00ffffff,那么ID就是该客户端的IP,否则就是该客户端的回调ID,由服务器分配。
5.1 连接到低ID客户端
要想连接到低ID客户端,必须发送OP_REQCALLBACK包给当前连接的服务器,这个包含了我们要连接的客户端的ID。
        OP_REQCALLBACK  = 0x1c,   //!< <u32>id
如果被要求连接的低ID客户端已经连接到服务器,那么服务器发送OP_CBREQUESTED包给它。
        OP_CBREQUESTED  = 0x35,   //!< <u32>ip<u16>tcpport
如果被要求连接的低ID客户端由于某些原因没有连接到这个服务器,那么服务器将会回应OP_CALLBACKFAIL.
        OP_CALLBACKFAIL = 0x36    //!< empty
posted @ 2007-04-26 11:13  津渡卧龙  阅读(2319)  评论(0编辑  收藏  举报