📄 emule源代码学习心得.txt
字号:
在kademlia\io\DataIO.cpp中实现的CDataIO中,还另外实现了按照Tag进行读写的功能。这在网络上交换共享文件的元信息非常重要,通常一个文件的元信息就可以分解成很多的Tag,如"文件名=xxx","文件长度=xxx"等等。也就是说,一个Tag就是表示某项属性等于某个值这样一个事实。在Opcodes.h这个文件中定义了很多的代码,其中就有很多常见的Tag的属性名称。CDataIO类中存储Tag的属性名都是先存一个字节的类型,再存名称,最后按照类型存值。
eMule中的这几项基础设施都是编写得比较好的,可以很方便得拿出来复用。像字符串编码的处理和具有一定数据结构的文件IO操作在很多地方都会很有用。eMule中这些类的实现基本上复制到其它的工程文件中只要稍微修改一下很快就能使用。以后我们还将看到eMule中很多其它很有用的基础设施。
4. eMule源代码学习心得(4):对自己的资源要了如指掌,CKnownFileList类的作用
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保存和读取它的列表中的所有文件的信息。
5. eMule源代码学习心得(5):分块机制--正确传输资源的保证
为了加快内容分发的速度,分块处理是一种简单有效的方法。emule中对每个文件都进行了分块处理。另外分块还有一个好处就是如果保留了每一分块的hash值,就能在只下载到文件的一部分时判断出下载内容的有效性。emule在获取每个共享文件的信息时,就对它进行了分块处理,因此如果要知道emule中的分块处理和恢复机制,看CKnownFile::CreateFromFile函数的实现就行了。
这个函数中牵涉到的和分块处理以及hash计算相关的类都在SHAHashSet.cpp和SHAHashSet.h中。下面介绍其中几个主要的类:
CAICHHash类只负责一块hash值,提供两个CAICHHash类之间的直接赋值,比较等基本操作。CAICHHashAlgo是一个hash算法的通用的接口,其它hash算法只要实现这种接口都能使用,这样,可以很方便得使用不同的hash算法来计算hash值。CAICHHashTree则是一个树状的hash值组织方式,它有一个左子树和右子树成员变量,类型是指向CAICHHashTree的指针,这是一个典型的实现树状结构的方法。CAICHHashSet中包含了一个CAICHHashTree类型的变量,它直接向CKnownFile负责,代表的是一个文件的分块信息。
SHAHashSet.h文件的开始的注释部分向我们解释了它的分块的方式。这里要用到两个常量9728000和184320,它们分别是9500k和180k。这是emule中两种不同粒度的分块方式,即首先把一个很大的文件分割成若干个9500k的块,把这些块组织成一颗树状的结构,然后每一个这样的块又分解成若干个180k的块(52块,再加一个140k的块),仍然按照树状的结构组织起来。最后总的结构还是一颗树。
CKnownFile::CreateFromFile方法是在读取目标文件的内容时,逐步建立起这样一颗树的。CAICHHashTree::FindHash能够根据读取到的目标文件的偏移量和下一块的大小,来找出对应的树枝节点(就是一个指向CAICHHashTree的指针)。如果有必要的话,还会自动创建这些树枝节点。因此在进行分块操作的时候,把文件从头到尾读一边,整个CAICHHashTree就建立起来了,对应的分块hash值也赋值好了。最后我们还需要注意的就是CKnownFile类中的hashlist变量。就是说它还单独保留直接以9728000字节为单位的所有分块的MD4算法的hash值。这样对于一个文件就有了两套分块验证的机制,能够适应不同场合。
6. eMule源代码学习心得(6):网络基础设施--网络基础设施的基础设施
MFC中已经有一些网络基础设施类,如CAsyncSocket等。但是emule在设计中,为了能够更加高效得开发网络相关的代码,构建了另外的一些类作为基础设施,这些基础设施类的代码也有很高的复用价值。
首先是CAsyncSocketEx类。AsyncSocketEx.h中对这个类的特点已经给出了一定的说明。它完全兼容CAsyncSocket类,即把应用程序中所以的CAsyncSocket换成CAsyncSocketEx,程序仍然能够和原来的功能相同,因此在使用上更加方便。但是在这个基础上,它的效率更高,主要是在消息分发机制上,即它处理和SOCKET相关的消息的效率要比原始的MFC的CAsyncSocket类更高。另外,CAsyncSocketEx类支持通过实现CAsyncSocketExLayer类的方式,将一个SOCKET分成若干个层,从而可以很方便得实现许多网络功能,如设置代理,或者是使用SSL进行加密等。
另外还有ThrottledSocket.h中定义的ThrottledControlSocket类和ThrottledFileSocket类,这两个类只定义了两个接口。任何其它的网络套接字类如果想实现限速的功能,只需要在其默认的发送函数(如Send或Sendto)中不发送数据而是把数据缓存起来,然后在实现ThrottledControlSocket或者ThrottledFileSocket接口中的SendFileAndControlData或SendControlData方法时才真正把数据发送出去,这样就能实现上传限速,而这也是需要UploadBandwidthThrottler类进行配合,UploadBandwidthThrottler是一个WinThread的子类,平时单独运行一个线程。下一次会详细描述它是如何控制全局的上传速度的。
7. eMule源代码学习心得(7):网络基础设施--全局限速器UploadBandwidthThrottler
UploadBandwidthThrottler是emule中使用的全局的上传限速器。它继承了CWinThread类,且在该类被创建的时候,就新创建一个线程开始单独运行。在该类被析构时也会自动停止相应的线程。这个线程的目标函数就是RunProc,然后为了避免在RunProc函数不能使用this指针的情况,它使用了RunInternal来实际完成工作线程的工作。在emule中,还有另外一个类LastCommonRouteFinder有类似的结构。
UploadBandwidthThrottler中保存了若干的套接字(Socket)队列,这些队列的处理方式略有不同。在标准队列(m_StandardOrder_list)里面排队的都是实现了ThrottledFileSocket接口的类,通常这些类能够传输文件内容也可以传输控制信息。而其它四个队列都是实现ThrottledControlSocket接口的类的队列,在这些队列中的类主要以传输控制信息为主。这四个队列为临时高优先级,临时普通优先级,正式高优先级,正式普通优先级。和把套件字直接添加到普通队列(AddToStandardList)不同,QueueForSendingControlPacket把要添加到队列的套接字全部添加到两个临时队列。根据它们的优先级添加到普通的临时队列。在RunInternal的大循环中,临时队列中的项目先被移到普通队列中,然后再进行处理。
UploadBandwidthThrottler使用了两个临界区,两个事件。pauseEvent是用来暂停整个大循环的动作的。而threadEndedEvent是标志整个线程停止的事件。sendLocker是大循环中使用的主要的临界区,而tempQueueLocker是为两个临时队列额外添加的锁,这样可以一边发送已有队列中的套界字要发送的数据,一边把新的套接字加到队列中。
UploadBandwidthThrottler的RunInternal中的大循环是该工作线程的日常操作。这个大循环中做了以下事情,计算本次配额,即本次循环中能够发送多少字节,好安排调度,计算本次循环应该睡眠多少时间,然后进行相应的睡眠,从而进行限速。操作控制信息队列,发送该队列中的数据,注意,控制队列中的套接字(m_ControlQueueFirst_list和m_ControlQueue_list)只使用一次就离开队列。而标准队列中的套接字不会这样。在一轮循环结束后,如果还有没有用完的发送数据的配额,则会有部分配额保存到下一轮。
8. eMule源代码学习心得(8):网络基础设施--emule套接字CEMSocket
CEMSocket是CAsyncSocketEx和ThrottledFileSocket的子类,它把若干功能整合到了一起,因此可以作为emule使用起来比较方便的套接字。例如它可以很方便得指定代理,把CAsyncSocketEx中的创建一个新的代理层并且添加到列表中的功能对外屏蔽了。另外它可以分出状态,如当前是否在发送控制信息等。
CEMSocket中我们需要仔细考察的是它的SendControlData和SendFileAndControlData方法。如前所述,这些方法是用来和UploadBandwidthThrottler进行配合,以便完成全局的限速功能的。它的功能应该是按照UploadBandwidthThrottler的要求,在本次轮到它发送数据时发送指定数量的字节数。因此,应用程序的其它部分在使用CEMSocket时,如果要达到上传数据限速的目的,不应该直接调用标准的Send或者SendTo方法,而是调用SendPacket。这里就有了另外一个结构Packet,它通常包含一个emule协议中完整的包,例如有协议的头部数据等,还内置了PackPacket和UnPackPacket方法,可以自行进行压缩和解压的功能。SendPacket把要发送的Packet放到自己的队列中,这个队列也有两个,控制信息包队列,和标准信息包队列。如果有必要,把自己加入到UploadBandwidthThrottler的队列中。
我们注意到CEMSocket的SendControlData和SendFileAndControlData方法其实都是调用自己的另一个重载的Send方法。而且我们也已经知道这个方法是在UploadBandwidthThrottler的工作线程中的大循环中被调用的,而这个Send方法的内容本身也是一个大循环,但是意义很明了,就是在不超过自己本次发送的配额的情况下,把自己的包队列中的包取出来,并且发出去。同样,这里也用到了一个临界区,它是为了保证从包队列中取出包来发送和把包往队列中放的操作是互斥的。因此,如果把它和UploadBandwidthThrottler结合起来,我们就看到了一个两层的队列,即所有的套接字组成了一个发送队列,在UploadBandwidthThrottler的控制下保证了对速度的限制,而每个套接字即将发送的数据包又组成了一个队列,保证了每次进行数据发送的时候都会满足UploadBandwidthThrottler的要求。
9. eMule源代码学习心得(9):搜索信息集-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只负责和搜索有关的信息的储存和读取,本身并不进行搜索。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -