转 eMule源代码解析(三)

来源:互联网 发布:非诚勿扰程序员罗亮 编辑:程序博客网 时间:2024/05/16 14:49
转 eMule源代码解析(三)

搜索信息集-CSearchList

CSearchList是emule中的搜索列表,掌管emule中所有的搜索请求。CSearchFile是这个列表中的元素,代表了一次搜索的相关信息。它们的关系和之前描述的已知文件和已知文件列表有一些类似的地方。CSearchList的主要任务就是对其一个叫做list的类型为CSearchFile列表的内部变量进行维护,提供很方便得往这个列表中添加,删除,查询,变更等操作的接口。另外,每一个搜索都有一个ID,是一个32位的整数。CSearchList中记录了每个搜索目前搜到的文件个数和源的个数(m_foundFilesCount和m_foundSourcesCount)。

CSearchFile是CAbstractFile的另一个子类(CKnownFile也是),它保存了某个文件和搜索相关的信息,而不是这个文件本身的信息(这些信息在CAbstractFile中已经包括了),这些和搜索有关的信息就是都在哪些机器上有这个文件,以及哪个服务器上搜到的这个文件。甚至还可以向搜索文件添加预览。在这个类的定义中嵌套定义了两个简单的结构SServer和SClient,表示了该搜索文件的可能来源,服务器或者其它客户端。m_aClients和m_aServers是这两个简单结构的一个数组,CSearchFile自然也提供了对这个数组的操作的接口,方便CSearchList使用。

CSearchList对外提供了搜索表达的接口,即每当有一个新的搜索提交时CSearchList::NewSearch会建立一个新的搜索项,但是此时还没有任何对应的搜索文件,因此只是在文件个数和搜索ID的对应表(m_foundFilesCount和m_foundSourcesCount)中建立新的项目。另外当有搜索结果返回时ProcessSearchAnswer或ProcessUDPSearchAnswer能够对返回的包直接做处理,创建相应的搜索文件信息CSearchFile对象,并加入到自己的列表中。当然,要把重复的搜索结果去除,发现同一个hash的文件的多个源时也会给它们建立一个二级列表(CSearchFile::m_list_parent)。现在我们可以看出,CSearchList只负责和搜索有关的信息的储存和读取,本身并不进行搜索。  

服务器信息集-CServerList

尽管目前有了Kad网络,但是使用服务器来获取各个emule用户的共享文件列表仍然是emule中主要的资源获取方式。CServerList就是emule中负责管理服务器列表的类。和前面若干列表类结构类似,CServerList需要对外提供列表的增加,删除,查找,修改等接口。在CServerList中,每个服务器的信息是一个CServer类。和搜索信息不一样,但是和已知文件列表一样,服务器的信息列表是需要长期保留的,因此CServerList和CKnownFileList类一样提供了把它所包含的所有信息保存到一个文件中,以及从这个文件中读回其信息的功能

CServer中的结构比较简单,只需要保留服务器的各种信息即可。它可以通过IP地址和端口来创建,也可以通过一个简单的结构ServerMet_Struct来创建,其中后者是用来直接从文件中读取的。该结构仅仅包含IP地址和端口以及属性的个数,CServer中其它的属性在保存到文件中时,均采用Tag方式保存。

CServerList除了提供通常的CServer信息外,还提供一些统计信息诸如所有的服务器的用户数,共享的文件数等。这些统计信息也是基于每个单独的CServer的相关信息计算出来的。

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来完成。

其它类型包没有必要全部都列出来,以后可以通过在分析其它部分时,因为牵涉到往服务器发送或者接受数据的时候再进行相应的分析。

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

客户端和客户端之间的TCP通信由CListenSocket和CClientReqSocket完成。这也是提供网络服务的应用程序的典型写法。其中CListenSocket只是CAsyncSocketEx的子类,只负责监听某个TCP端口。它只是内部有一个CClientReqSocket类的列表。而CClientReqSocket是CEMSocket的子类,因此它能够自动完成emule的packet识别工作。它有ProcessPacket和ProcessExtPacket来处理客户端和客户端之间的包,其中前者是经典的eDonkey协议的包,后者是emule扩展协议的包。

CListenSocket和CClientReqSocket类之间的关系和前面分析的列表类和它对应的成员类的关系是相似的,CListenSocket提供对自身的CClientReqSocket列表中的元素的增加,查询,删除等操作。同时也维护关于这些成员的一些统计信息。我们注意到CListenSocket在其构造函数中就把自己添加到CListenSocket类(theApp.listensocket,该类的唯一实际示例)的列表中。

CClientReqSocket类和CUpDownClient类之间存在着对应关系。它们都表示了另外一个客户端的一些信息,但是CClientReqSocket类主要侧重在网络数据方面,即负责两边的互相通信,而CUpDownClient类负责的是从逻辑上对网络另一边的一个客户端进行表达。CUpDownClient类代码很长,以后再说。