您的位置:首页 > 编程语言

eMule 的使用场景及初始化任务描述级eMule相关代码分析(仅涉及ed2k)

2016-04-29 21:06 246 查看
提前说明:从这页博客开始就会涉及eMule源代码的分析(或者说梳理更为合适),梳理源代码的目的在于让大家在看了一下原理后,想看下这些原理的具体实现(看eMule源代码)时轻松些,更有目标。所以在这里笔者也上传了eMule官方源码 http://download.csdn.net/detail/huang_rong12/9506732 ,若梳理的不好请见谅。

前面 已经介绍了ed2k的相关概念 ,本博客会先描述emule客户端使用场景,然后按照使用场景逐步进行分析。

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

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

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

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

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

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

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

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

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

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

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

这样就是一个文件下载的基本过程(有些已经简化(如返回的源客户端可能有多个(文件可能已经被分段了))但是基本过程已经描述清楚了)。

在场景描述中提到了一个很重要的东西---ed2k服务器。Ed2k服务器很重要是由于下载文件的过程离不开他。第一必须连上ed2k服务器才能开始下载。第二,必须通过ed2k服务器才能收索资源,并解析ed2k连接,得到文件源客户端的信息。而且客户端与源客户端传递文件数据块也受ed2k服务器影响(高用户id和低用户id)。

现在常用ed2k服务器软件是 Lugdunum 。这款软甲是不开源但免费使用的。所谓的ed2k服务器就是安装了ed2k服务器软件并接入ed2k网络中另一台ed2k服务器的电脑。

一.启动客户端,初始化信息,这里主要做的事情是从emule的配置文件中读取到ed2k服务器列表的信息(值得说明的是这里的配置文件不一定存放在本地也有可能是放在网络中的某台服务器上但是在eMule客户端的实现中是放在本地Conf文件夹里,配置文件没放在本地的主要是现在商用的ed2k软件(包括qq旋风,迅雷),之所以采用配置信息不放在本地的方式,笔者认为这些软件并不希望由用户自己改动ed2k服务器的地址信息,而是由后台统一设置,这样可提高ed2k网络的稳定性)。总的来说,就是第一步是完成后,客户端就可以知道ed2k服务器的列表(一般不是仅一个服务器,通常是多个(原因是客户端得到服务器地址,但不知服务器的连接情况,可能服务器此时接受响应的客户端已经很多了,这时客户端是无法连上的,所以一般情况会有多个服务器信息))。

第一步的文字描述就到这里。接下来结合eMule的源代码进行分析。

eMule的代码结构非常合理。虽然代码量比较大,但是各个功能模块之间的划分都很合理。从它的工程文件里面就可以看出这一点。eMule把表示功能的代码文件和表示界面的代码文件分开了,Source Files和Header Files是实现功能的代码的源文件和头文件,而Interface Source和Interface Header是实现图形界面的源文件和头文件。由于eMule的代码量太大,本系列将跳过图形界面的实现,着重分析eMule的功能实现部分的代码,而且功能实现方面也主要挑主要的部分分析。本节将从emule.cpp开始分析,引出eMule中使用到的几个主要的功能实现的类,并大体描述它们的作用,这里开始就可以对照着源码一步一步往下看了。

emule.cpp为类CemuleApp的实现。因此在运行时,首先会运行InitInstance进行一些初始化的工作。从这个函数里面我们也可以第一次看出那些即将在整个程序中发挥作用的类了。

最开始的时候是计算出程序常用的一些目录,如配置文件,日志文件等。接下来是ProcessCommandline,它的作用有两方面,第一是确认该eMule的运行方式,即命令行后面有没有参数,第二是确认目前eMule是不是只有一个实例在运行。在一般的情况下,双击eMule可执行文件是不会带参数的。但是通过点击链接或者打开关联文件的方式打开eMule则相当于带参数运行eMule。通过在注册表里添加一些项目可以让一个程序和某种链接或者某个后缀的文件产生关联。具体办法可以参见OtherFunctions.cpp中的Ask4RegFix,BackupReg,RevertReg三个函数的功能。ProcessCommandline中通过创建带有名称的互斥信号量来确认是否有其它的eMule实例在运行。对于一个确定的名称,CreateMutex只能创建一个互斥信号量。因此通过该信号量是否创建成功就可以知道是否有其它eMule实例运行。如果有的话,而且又是带参数的那种模式,那么直接把这个参数使用Windows的消息机制发给那个窗口即可,接下来的代码无非就是如何找到另外一个叫"eMule"的家伙以及给它发个什么消息。pstrPendingLink是一个全局变量,表示将要被处理的命令行参数。它将会在初始化完成后一段时间后被处理。

下面两个比较重要的类是CPreferences和CStatistics。前者掌握着程序的大部分配置数据,后者则进行各种统计。它们的特点都是有很多的成员变量,而且还是静态的,这种方式可以保证它们的唯一性,而且把这些变量统一到一个类管理。但是实际上并不需要了解每个变量的含义。thePrefs和theStats是这两个类的唯一的实例。

在emule.cpp的InitInstance 函数中有 如下两行代码

// create & initalize all the important stuff

thePrefs.Init();(从配置文件中读取配置息相关信息并初始化相应数据结构以存储信息)

theStats.Init();(从配置文件中读取统计相关信息并初始化相应数据结构以存储信息)

这两行就是完成初始化,这样InitInstance 函数结束之后,通过thePrefs就可以找到服务器信息了。

eMule 服务器及服务器列表相关类

CServerList就是emule中负责管理服务器列表的类。是列表类结构,CServerList需要对外提供列表的增加,删除,查找,修改等接口。在CServerList中,每个服务器的信息是一个CServer类。和搜索信息不一样,但是和已知文件列表一样,服务器的信息列表是需要长期保留的,因此CServerList和CKnownFileList类一样提供了把它所包含的所有信息保存到一个文件中,以及从这个文件中读回其信息的功能。

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

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

在处理完其它一些事情,包括创建图形界面对象CemuleDlg后,接下来可以看到一排一排的创建新对象的语句。这些类将会实现eMule程序运行的主要功能,

eMule 读取配置文件相关代码(类)。

eMule中要读取的配置文件数量较多,每种配置文件都是自己定义的格式,为了方便读取和存储这些文件,eMule中有一个很重要的基础设施类来复制这些文件操作,它能够很方便得处理一些常用数据类型的读写,并且带有一定的安全保护机制。这项基础设施在SafeFile.cpp和SafeFile.h中实现。在kademlia\io目录下有这项功能的另外一项实现。它们实现的功能基本上相似,但是kademlia\io目录下的版本实现的时候多了一个以Tag作为单位进行读写的功能。这些实现中和另外一项基础设施,那就是字符串转化密切相关。StringConversion.cpp和StringConversion.h是eMule中专门复制各类字符串转化的基础设施,什么Unicode啊,多字节流啊,或者是UTF-8之类的,在这里转化全部都不是问题。关于字符串转化,个人推荐尽量使用Unicode的宽字符,这样可以最大程度得避免乱码。

SafeFile.cpp或者kademlia\io目录下的实现都有这样的特点,那就是把数据操作的行为和数据操作的对象分割开来。它们都定义了一个抽象的数据操作的基类(在SafeFile.cpp中是CFileDataIO,在kademlia目录下是DataIO.cpp实现的Kademlia::CDataIO),这个类中只负责实现在逻辑上操作一项数据的行为,例如,要读取出一个32位的整型,那么就是读出四个字节到一个整型数值的地址中,要读取或者写入其它类型的数据用的是类似的方法。但是这个类把物理上进行数据操作的方法全部都声明为纯虚函数,即读出多少个字节,写入多少个字节这样的。有了这样一个基类,就可以非常方便得在它上面进行重载,把这些纯虚函数定义为向某块内存中进行读写的操作,就能很方便得将较为复杂的数据序列化到一块连续的内存,而如果这些纯虚函数是向文件读写的操作,那么自然就可以很方便得用来读写各种格式比较奇怪的自己定义的配置文件了。

这些类要读取的数据对象通常有这些,各种整型,字符串,以及Tag类型。整型读写起来比较简单,从1个字节的,2个字节的到4个,8个或者16个字节类型的数据读写方法都比较类似。这里要稍微提一下16个字节的那种,16个字节是128位,是eMule中的Kad网的随机生成的ID的长度,也是eMule中常用的MD4的hash算法生成的结果的长度。通常用来直接存取整个的这样的一个ID。在kademlia\utils目录下的UInt128.cpp实现了一个表示128位的整数的类,功能十分完善,可以进行一些算术操作,并且可以进行比较,这样为它以后作为key出现在hash表中打下了基础。仔细学习UInt128.cpp中的代码实现可以学到很多在编写这种自定义的数据对象类型时应该注意的问题。

数据操作中另外一项很重要的操作是字符串,总的原则是先写一个长度,再写内容。但是到具体的操作的时候就需要注意这些细节了,如长度是写4个字节还是两个字节,字符串的内容要不要用UTF-8进行编码。这些操作就需要和StringConversion.cpp紧密合作了。其实后者的字符串转化函数很多也是调用ATL的相关函数,只是在外面再包上一层MFC的CString。

在kademlia\io\DataIO.cpp中实现的CDataIO中,还另外实现了按照Tag进行读写的功能。这在网络上交换共享文件的元信息非常重要,通常一个文件的元信息就可以分解成很多的Tag,如"文件名=xxx","文件长度=xxx"等等。也就是说,一个Tag就是表示某项属性等于某个值这样一个事实。在Opcodes.h这个文件中定义了很多的代码,其中就有很多常见的Tag的属性名称。CDataIO类中存储Tag的属性名都是先存一个字节的类型,再存名称,最后按照类型存值。

eMule中的这几项基础设施都是编写得比较好的,可以很方便得拿出来复用。像字符串编码的处理和具有一定数据结构的文件IO操作在很多地方都会很有用。eMule中这些类的实现基本上复制到其它的工程文件中只要稍微修改一下很快就能使用。以后我们还将看到eMule中很多其它很有用的基础设施。

emule作为一个文件共享方面的程序,首先要对自己共享的所有的文件的信息都十分清楚,类CKnownFileList的作用就是这样的,它在emule.cpp中随着cmuleapp类创建的时候被创建。

CKnownFileList类使用了MFC的CMap类来维护内部的hash表,这也可以看出emule和MFC的关系确实非常紧密。这里如果用STL的map其实也是可以的。它内部维护了一个已知的文件的列表和取消了的文件列表。这些hash表的关键字都是文件的hash值。这样能够判断出文件名不同而内容相同的文件,而一般要让不同内容的文件有相同的hash值是非常困难的,这也是hash函数它设计的初衷。因此除非是碰上王小云教授这样的牛人,我们基本上可以认为,两个文件hash值相同就代表了它们内容相同。再来看CKnownFileList.cpp,这个文件其实并不长,因为管理一个列表确实不需要太多种类的操作,如果对于每个具体的文件有一个很强大的类来处理它的话。而这里确实有,它就是CKnownFile。有了这么一个类,我们就可以看到,CKnownFileList类所需要做的工作就是能够根据一些信息查找到对应的CKnownFile类,能够复制其它的列表中的信息,能够把所有的这些信息存成文件,然后下次emule运行的时候能够把这些信息快速恢复出来,最重要的是能够在完成以上工作的情况下不造成内存泄漏。

CKnownFile类就是一个专门关注某个特定文件的信息的类,它仍然有其基类CAbstractFile。但是它和CAbstractFile类的主要区别就是CAbstractFile类只有基本的信息存取的功能,而CKnownFile能够主动的生成这些信息,例如,给一个文件的路径给CKnownFile,它能够主动地去获取和这个文件有关的一切信息,并且把它保存在自己的成员变量里(CreateFromFile)。CKnownFile.cpp文件看上去比较长,是因为它做的工作比较多,现在版本的emule中,除了对某个文件进行全文hash以外,还采用了BT的方式,进行分块hash,这样在传输文件的时候,即使发生出错的情况,也可以不必重传整个文件,而只是重传有错误的那块,这种机制叫做高级智能损坏处理(AICH,Advanced Intelligent Corruption Handling),这个机制以后再继续分析。

CKnownFile把读到的文件信息都保存成一个一个的Tag。它在运行中会尽量得获取更多的文件信息,例如,对于媒体类型的文件,它能够调用id3lib库来获取诸如作者,唱片发行年代,风格等tag信息。如果是视频媒体文件,它还会去抓图(功能实现:CFrameGrabThread)。

CKnownFile还能够随时掌握目前该文件的下载情况(内部有个CUpDownClient的列表),当然,还会根据要求序列化和反序列化自己,LoadFromFile和WriteToFile都以CFileDataIO为参数,这样方便CKnownFileList保存和读取它的列表中的所有文件的信息。

到这里第一步涉及的分析内容就到结束了,读者可以根据本博客的梳理,开始eMule相关源码(初始化部分)的深入研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: