eMule连接到进ed2k网络分析

上一篇博客已经介绍eMule客户端的初始化过程及相关代码梳理,这里将接着介绍第二个步骤 连接到ed2k服务器

 

Emule 客户端使用场景分析,当我们要下载一个ed2k 文件时 ,可以分为以下几步:

一 。启动emule客户端(这时候就开始初始化,主要是对Config文件夹下的文件(比如Server.met等)进行反序列化,生成相应实例,存储信息(包括ed2k服务器地址及端口号),相关博客)。

二. 客户端必须连上某个服务器(可能会从服务器列表中依次尝试,直到连上为止,连上以后客户端就加入了emule网络,成为协议节点)。

三. 用户就向服务器传递ed2k连接(这是用户已知道ed2k连接的情况,如果用户不知道ed2k连接,就可向服务器发送关键词,服务器会返回很多链接,这时再提交连接就一样的过程了)。

四. 提交ed2k连接以后,就表示用户要下载文件,服务器就返回存储文件的文件源客户端。

五. 用户得到文件源客户端的信息后,就发起到某个源客户端的回话;

六. 原客户端收到会话请求后就排队等待上传文件(排队的是请求发起客户端);

七. 原客户端选择用户客户端,开始上传;

八. 用户客户端从原客户端下载文件数据库块。

九. 结束下载完成会话(用户客户端)。

十. 源文件客户端从队列中选择一个等待客户端,进行数据传送。

 

上一篇博客中,得到了服务器列表这里就开始连接服务器(这里会涉及到一些概念,例如客户端id,扩展协议,若不清楚的请看另一篇博客,描述了相关概念,),还得提前说明,这里的连接不仅是数据可以发过去(可以相互交换数据),而是连接到ed2k网络(在交换信息的基础上还得登陆成功),在准备建立与服务器的连接时,客户端会尝试并行地连接到几个服务器,根据成功的登陆顺序放弃其他的。

有下面几个可能的连接建立个案:

1、高ID连接-服务器分配一个高ID给正在连接的客户端

2、低ID连接-服务器分配一个低ID给正在连接的客户端

3、拒绝会话-服务器拒绝客户端

以上是可以交换数据的前提。如果不能交换数据的话,就会是以下情况。

服务器崩溃或者不可连接。

eMule连接到进ed2k网络分析_第1张图片

2.1描述了导致高ID连接的信息顺序。在这种情况下,客户端建立一个TCP连接到服务器,然后发送一个登录信息到服务器。服务器用另一个TCP连接到客户端,执行一个客户端-客户端的握手来保证连接的客户端有能力接收来自其他eMule客户端的连接。在完成客户端握手后,服务器关闭第二个连接,通过发送ID更改信息来完成客户端-服务器的握手。你可能注意到eMule信息消息是灰色的。这是因为这个消息是eMule协议扩展的一个部分


eMule连接到进ed2k网络分析_第2张图片

2.2描述了导致低ID连接的信息顺序。在这种情况下,服务器不能连接到发送请求的客户端,分配一个低ID给客户端。服务器消息一般包含警告信息,就像警告[服务器细节] - 你是低ID。请察看你的网络配置和/或你的设置ID和高ID握手都是通过随着ID更改消息完成的,这个ID更改消息分配客户端一个客户端ID,用在与服务器的下一个会话。


eMule连接到进ed2k网络分析_第3张图片

2.3描述了被拒绝的会话顺序。因为客户端拥有一个低ID或者到达了服务器硬件的容量限制,服务器就可能拒绝会话。服务器消息会包含一个短字符串描述拒绝的理由。

 总结一下只有低id登录ed2k服务器并成功时,才会包含扩展协议,至于失败登录失败或者高id登录并成功都不会包含扩展协议,还有就是建立TCP连接后还要请求登录,登录成功后才是连接上了ed2k网络。

接下来就得开始确定登录请求的具体内容(笔者从一个翻译文档中得到如下消息编码说明(只截取了和登录相关的以及一些基础知识))

6.1 常规消息编码说明

这部分主要描述了建立在TCP/UDP基础上的常规消息编码说明

6.1.1 Endianity

所有的消息都是以little-endian规则进行编码的不是传统网络上的以big-endian顺序进行编码。这可以很容易的解释为什么基于Microsoft Windows的客户端/服务器程序用在Intel处理器上。

6.1.2 消息头

所有的消息都包含有一个6字节的消息头,结构如下:

 

1. 协议- 一个1字节的标示符 - eDonkey是0xE3 , eMule是0xC5 

2. 大小- 消息大小为4字节 - 不包括标题和size字段的信息大小

3. 种类- 一个一位字节 – 一个唯一的消息 ID

6.1.3 消息标记

标记是一个TLV-like(种类,长度,内容)结构,用来传输可选择的数据。有好几种标记,他们将在本章中被一一列出。当读者想要一些有特别用途的特定标记时,可以以本章中精确定义的一些结构作为参考。一个标记拥有4部分,他们在消息中并不是连续的:

1type– 1字节的整型

2name –可以是下面中的任意一种

• 可变长的string类型

• 1字节的整型

3value -可以是下面中的任意一种

• 4字节的整型

• 4字节的浮点型

•可变长的string类型

4Special- 1字节的整型,特殊标记制定位

整型的我们叫做整型标记,对于浮点型和字符串型也一样。字符串标记有两种,一种是3位的整型另外一种是4位的浮点型。他们按照上面定下来的顺序进行编码比如第一个是type,然后是name,最后是value。Type占一个字节。Name占一个两个字节的结构及可以是String的 也可以是 Integer的。例如Integer名字是0x15将被编码为0x01 0x00 0x15。定值域(比如整型和浮点型数字)按照规定的填写,string值按一样的长度编码,但是治还是不变的。注意name没有特别的意义只是为了将来更容易的描述消息。

6.2 客户服务器的TCP信息

这个章节描述了服务器与客户端之间使用TCP port传递的信息。

6.2.1 登入

登入信息是TCP连接完成后由客户端向服务端发出的首个信息。信息的长度的变化取决于如用户昵称的用户设置。为什么应该发送客户端TCP传输还不清楚,在TCP标题中也会发生。

名称

字节数

默认值

注释

Protocol

1

0xE3

 

Size

4

 

不包括标题和size字段的信息大小

Type

1

0x01

OP_LOGINREQUEST opcode

Client Hash

16

 

详细的信息见section 1.4(对应本博客的基础概念部分)

Client ID

4

0

发送第一次连接的Client ID通常为0.详见section 1.3(对应本博客的基础概念部分)

TCP port

2

4662

TCP port是客户端使用的,是可以配置的

Tag Count

4

4

紧跟着信息的标签数

Name Tag

不定

N/A

用户的昵称(是可以在软件中配置的)。

Tag是一个标签串并且标签的名字是值为0x1的整数。

Version Tag

8

0x3C

由客户端支持的eDonkey版本。

Tag是一个整数标签并且标签的名字是一个值为0x11的整数。

Port tag

8

4662

TCP port是由客户端使用的。

Tag是一个整数标签并且标签的名字是一个值为0x0F的整数。

Flags Tag

8

0x01

Tag是一个整数标签并且标签的名字是一个值为0x20的整数。

 

6.2.2服务器消息

服务器消息是在不同情况下由服务器发向客户端的长度可变的消息,首要的情况是客户端马上发送登录请求后。一个简单的服务器信息可能包括几个由新分隔符分开的一些信息。(‘\r,’\n’,或者两者都有)。由“server version,”warning”,”error”和”[emDynIP:”的信息对客户端来说有特殊的含义。 其它信息简单得显示给用户即可。

名称

字节数

默认值

注释

Protocol

1

0xE3

 

Size

4

 

不包括标题和size字段的信息大小

Type

1

0x01

OP_LOGINREQUEST opcode

Size

2

N/A

不包括到现在为止描述过的领域中的信息剩余的字节数

Messages

不定

N/A

new lines分割的服务器信息清单

特别信息

1. version- 经常在一个成功的同步交换连接中返送

2. error-

3. warning- 经常在服务器拒绝连接或者客户端中有low ID的时候发送

4. emDynIP

 

从上面的结构中可以知道客户端登录请求要包含的数据及服务器端返回的数据。但是就算这样,还是不怎样把信息传上去。这就只有分析eMule源码了

 

emule的通信协议-一些基本的约定

 

接下来将不可避免得要碰到emule的协议。emule的通信协议格式设计成一种便于扩充的格式。对于TCP连接来说,连接中的数据流都能够划分成为一个一个的Packet,CEMSocket类中就完成了把接收到的数据划分成Packet这一工作。但是具体的对于每个Packet进行处理的工作被转移到它的子类中进行。CEMSocket类的两个子类CServerSocket和CClientReqSocket所代表的TCP连接就分别是客户端和服务器之间的TCP连接以及客户端之间的TCP连接。在数据流中的第一个字节代表的是通信的协议簇代码,如0xE3为标准的edonkey协议,0xE4为kademlia协议等等。接下来的四个字节代表包内容的长度,所有的包都用这种方式发送到TCP流中,就可以区分出来了。另外每个包内容中的第一个字节为opcode,即在确定了某个具体协议后,这个opcode确定了这个包的具体含义。

 

 

对于走UDP协议的包,处理起来更加得简单,因为UDP本来就是以一个包一个包作为单位在网络上流传的,因此不需要在包的内容中再包含表示长度的字段。每个UDP包的第一个字节是协议簇代码,其它内容就是包的内容。CClientUDPSocket类负责处理客户端和客户端之间的UDP包,而CUDPSocket类负责处理客户端和服务器之间的UDP包。另外还有个Kademlia::CKademliaUDPListener类,专门处理和Kademlia协议相关的UDP包。

 

最后说一下Packet类,这个类以前只是提到过。它是emule的通信协议的最小单位。我们可以看出,它的构造函数有多个版本,这也是为了可以用不同的方式来创建Packet。例如只包含一个头部信息的缓冲区,或者只是指定协议簇代码等。而且它内部实现了压缩和解压的方法,该方法直接调用zlib库中的压缩方法,可以减少数据的传输量。这里要注意一点的就是压缩的时候协议簇代码是不参与压缩的,压缩完毕后会更换协议簇代码,例如代码为标准edonkey协议0xE3的包在压缩后,协议代码就变成0xD4了,这里进行协议代码变化是为了使接受方能够正确识别并且进行相应的解压操作。

 

 

emule的通信协议-客户端和服务器之间的通信概述

这里不仅仅是登录,还包括查询等等和服务器之间的信息交换。

客户端和服务器之间的所有通信由类CServerConnect掌握。CServerConnect本身不是套接字的子类,但是它的成员变量CServerSocket类型的connectedsocket是。CServerConnect内部有一列表,可以保存若干CServerSocket类型的指针。但是这并不说明它平时连接到很多服务器上。它只是可以同时试图连接到若干个服务器上,这只是因为连接到服务器上的行为不一定能成功。

 

CServerSocket类是CEMSocket的子类,它比CEMSocket要多保存一些状态,比如当前的服务器连接状态。它同时还保留它当前所连接的服务器的信息。通过分析CServerSocket::ProcessPacket就可以直接把emule客户端和服务器之间的通信协议理解清楚,这里是服务器发回的包。TCP连接建立后的第一个包是在CServerConnect::ConnectionEstablished中发出的,即向服务器发出登陆信息。如果登陆成功,则能够从服务器处获取自己的ID,这是一个32位的长整数。如果这个数小于16777216,那么我们称它为LowID。具有LowID的客户端通常情况下其它客户端将不能直接连接它。得到LowID的原因比较多,例如当自己处于NAT的后端的时候。获取自己的ID后将会向服务器发送自己的共享文件列表,这一动作由共享文件列表类CSharedFileList来完成。

 


 

你可能感兴趣的:(ed2k,协议,kad协议,eMule源码分析梳理)