因为设计协议需要,看了下这个协议,顺便翻译了前5点,包括了服务器 - 客户端
客户端 - 客户端 通信相关的协议。
注:老婆一忙就不理我~郁闷阿!
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 |
|