一款P2P文件共享软件,电驴英文名eDonkey,它是一种档案分享网络,最初用于共享音乐、电影和软件。与多数文件共享网络一样,它是分布式的;文件基于点对点原理传输,而不是由中枢服务器提供。客户端程序连接到ed2k网络来共享文件。而ed2k服务器作为一个通讯中心,帮助用户在ed2k网络内查找文件。它的客户端和服务端可以工作于Windows、Macintosh、Linux、UNIX等操作系统。任何人都可以作为服务器加入这个网络。

项目如图:

共有8个项目!~~~~~

emule为电驴主要工程。

Zlib为数据压缩支持库,传输过程中支持数据压缩。

Id3lib为用于读、写和操纵ID3v1和ID3v2标签的对于媒体类型的文件,它能够调用id3lib库来获取诸如作者,唱片年代,风格等tag信息,如果是视频媒体文件,它还会抓图。

Png为提供对PNG文件处理的支持。

Resizable为一个界面库,可以根据父窗口的位置和大小动态调整控件窗口的大小。

Crypto51为密码类库,实现了各种公开密钥算法、对称加密算法、数字签名算法、信息摘要算法,而电驴用于实现RSA签名,支持独有的积分机制。

CxImage为图像处理库,与Windows、MFC支持极好,支持图像的多重操作(线性滤波、中值滤波、直方图操作、旋转缩放、区域选取、阈值处理、alpha混合等等)。

miniupnpc为操作局域网中所有的UPNP设备。

效果如图:

主程序共有五百多个目标文件,分析结合了网上的资源,主要分析如下:

当emule中开始使用Kademlia网络后,便不再会有中心服务器失效这样的问题了,因为在这个网络中,没有中心服务器,或者说,所有的用户都是服务器,所有的用户都是客户端。

 class CBufferedFileIO : public CStdioFile, public CDataIO//多文件操作缓冲
 class CByteIO : public CDataIO//扩展了字节操作数据
 class CContact//自定义的联系人信息类//主要包含对方的ip地址、ID、tcp端口、udp端口,Kad版本号以及其健康程度(健康程度有四个层次)
 class CDataIO//数据通信类 数据类型有byte、int8/16/32/64/128、hash、float、Bsob、String、Tag等
 class CEntry//输入 例如:文件名设置
 class CFileIO : public CFile, public CDataIO//扩展了文件数据操作
 class CIndexed//处理本地存储的索引信//利用了一些列的Map来存储这些对应消息,CMap是MFC中实现标准STL中的map的模版累,该类中包含了四个这样的类,分别用来//存储文件源信息、关键词信息、文件评论信息以及负载信息//其中文件评论信息是不长久保存的,而其它的信息都会在退出时候写到文件中//下载重启时再重新调入,另外负载信息不是等其它联系人来发布的,而是根据文件源信息和关键词信息的发布情况自行进行动态调整的//该类为其它部分的代码提供了它们所需要的增加信息和搜索信息的接口,这样在从网络中获取到的相关的搜索或者发布请求,并且//CKademliaUDPListener完成消息的解释后,就可以交给该类来进行处理。//一个文件源信息是一个文件内容的hash值和拥有这个文件的客户端的ip地址,各种端口号以及其它信息之间的对应关系,//而一个关键词信息则是该关键词和它对应的文件之间的关系
 class CIOException : public CException//输入输出异常处理
 class CKadClientSearcher//Kad客户端网络搜索 通过ip地址或id节点搜索
 class CKademlia//Kademlia网络的主控类,负责启动和关闭整个Kademlia网的相关代码,在它的Process函数中,会处理和Kademlia网相关的事务,例如://隔一段时间检查某个区间的节点数是否过少,如果是则寻找一些新的节点,另外经常对自己的邻居进行检查等,这些都是属于需要进行日常安排的//工作,所有搜索任务的日常处理也需要它来调度,它还作为Kademlia网的代表,向emule其它部分的代码返回kademlia网的一些统计信息
 class CKademliaError//自定义了Kademlia网络的错误信息
 class CKademliaUDPListener : public CPacketTracking//专门处理和Kademlia协议相关的UDP包//负责处理UDP网络信息,处理所有和Kademlia网相关的消息,工作已经在emule的普通UDP客户端处理代码那里处理好了//具体的消息分类:首先是健康检查方面的消息,一般是ping-pong机制,//对应的消息有KADEMLIA_HELLO_REQ和KADEMLIA_HELLO_RES,对本地联系人信息列表进行检查时,会对它们发送KADEMLIA_HELLO_REQ消息//最常用的消息是节点搜索消息,在Kademlia网络中,进行节点搜索是日常应用所需要传输的主要消息,它的实现方式是迭代式的搜索。

Kad网自定义了标识数据 有如下:bool Bsob Float Hash String Uint UInt16/32/64等等

 class CKeyEntry : public CEntry//关键词输入 可以搜索/匹配信息
 class CLookupHistory//自定义了查询历史记录
 class CMiscUtils//单元转换 例如:ip地址转换成字符串
 class CPacketTracking//封包跟踪
 class CPrefs//处理自身的Kademlia相关信息,它和emule普通代码中的CPreferences作用类似,但是CPrefs只保留和Kademlia网相关的,需要长期保存的本地信息,//主要就是本地的id
 class CRoutingBin//包含一个CContact的列表,要访问联系人的信息必须通过某个CRoutingBin,CRoutingZone内部是不直接包含联系人信息的//可以把新的联系人信息往一个特定的CRoutingBin中加,当然也可以进行联系人查找,它也提供方法能够寻找出离某个ID距离最近的联系人,并给出//这样的一个列表(一个CRoutingBin类中能够包含的CContact的数量的)
 class CRoutingZone//处理联系人数据结构的最上层,直接为Kademlia网提供操作接口,该类的结构为一个二叉树,内含两个CRoutingZone指向它的左子树和右子树//另外也包含一个CRoutingBin类型的指针,但是只有在当前的CRoutingZone类为整个二叉树的叶节点时,这个指向CRoutingBin类型的指针才有意义。
 class CSearch//一个具体的搜索任务,它包括了一个搜索任务从发起到结束的全部过程,要注意的是//搜索任务并不只是指搜索文件源或者关键词的任务//一次发布任务它也需要创建一个该类对象,并且让它开始执行//在创建的时候需要说明它的类型,例如是为了搜索节点还是搜索关键词信息或者文件源信息//启动,开始第一次从本地的联系人列表中寻找候选的联系人,然后开始发动搜索//void CSearch::Go()//向某个联系人发送一个搜索某id的联系人信息请求//void SendFindValue(CContact* pContact, bool bReAskMore = false);//在搜索进行到一定地步的时候,如果得到了一些 中间结果,开始进行下一步的行动,下一步的行动仍然可能是SendFindValue//也可能认为搜索到的联系人离目标已经足够近了,于是就可以开始实质性的请求//void JumpStart();//一个实质性的请求//void StorePacket();
 class CSearchManager//掌握所有的搜索任务,它包含了一个所有CSearch指针对象的CMap//使用CMap的原因是因为所有的CSearch都一定对应一个ID,这个ID就是该CSearch所对应的目标,不管是要查找节点//还是要搜索或者发布信息,一定都要找到和目标id相近的联系人//直接和Kademlia网的其它部分代码接触,例如:CKademliaUDPListener搜索到了一些结果,它会把这些结果交给该类//然后该类再去寻找这个结果是属于那个搜索任务的,并且进行转交//另外,该类对外提供创建各种新的搜索任务的接口,作用类拟于设计模式中的factory,其它部分的代码只需要说明//需要开始一个什么样的搜索任务即可
 class CUDPFirewallTester//Kad网针对UDP防火墙测试
 class CUInt128//实现对128位的ID的各种处理,并且内置其各种运算//处理一个128位的长整数
class C3DPreviewControl : public CStatic
//重载CStatic 实现3D效果
class CAbstractFile: public CObject
//  派生出三个类CCollectionFile,CKnownFile和CSearchFile,CAbstractFile类的基类是CObject
//  typedef CTypedPtrList<CPtrList, Kademlia::CEntry*> CKadEntryPtrList;
//这是一个模板类,使用CTypedPtrList类,必须添加afxtempl.h这个头文件,
//template < class BASE_CLASS, class TYPE > class
//CTypedPtrList : public BASE_CLASS
//              BASE_CLASS 类型指针列表类的基类;必须是一个指针列表类CObList或CPtrList。
//              TYPE 保存在基类列表中的元素的类型。
//              声明了从CPtrList派生的类型指针列表CKadEntryPtrList该列表存储并返回指向Kademlia::CEntry对象的指针。
//              SetFileName函数
//              void CAbstractFile::SetFileName(LPCTSTR pszFileName, bool bReplaceInvalidFileSystemChars, bool bAutoSetFileType)
//{
//  m_strFileName = pszFileName;
//  if (bReplaceInvalidFileSystemChars){//替换无效的字符,也就在替换文件名不能包含的字符
//      m_strFileName.Replace(_T('/'), _T('-'));
//      m_strFileName.Replace(_T('>'), _T('-'));
//      m_strFileName.Replace(_T('<'), _T('-'));
//      m_strFileName.Replace(_T('*'), _T('-'));
//      m_strFileName.Replace(_T(':'), _T('-'));
//      m_strFileName.Replace(_T('?'), _T('-'));
//      m_strFileName.Replace(_T('/"'), _T('-'));
//      m_strFileName.Replace(_T('//'), _T('-'));
//      m_strFileName.Replace(_T('|'), _T('-'));
//  }
//  if (bAutoSetFileType)
//      SetFileType(GetFileTypeByName(m_strFileName));//文件类型,视频,图片等
//}
//SetFileHas函数
//void CAbstractFile::SetFileHash(const uchar* pucFileHash)
//{
//  md4cpy(m_abyFileHash, pucFileHash);
//}
//Md4cpy是一个内联函数,用于拷贝md4算法生成的散列值
//__inline void md4cpy(void* dst, const void* src) {
//  ((uint32*)dst)[0] = ((uint32*)src)[0];
//  ((uint32*)dst)[1] = ((uint32*)src)[1];
//  ((uint32*)dst)[2] = ((uint32*)src)[2];
//  ((uint32*)dst)[3] = ((uint32*)src)[3];
//}
//__inline是Microsoft SpecificMd4算法生成的是128为散列值,所以一个uint32表示32位所以就有上面的四行代码.
//virtual void SetFileSize(EMFileSize nFileSize) { m_nFileSize = nFileSize; }
//oid CKnownFile::SetFileSize(EMFileSize nFileSize)
//{
//  CAbstractFile::SetFileSize(nFileSize);
//  m_pAICHHashSet->SetFileSize(nFileSize);
//  if (nFileSize == (uint64)0){//文件大小为的处理
//      ASSERT(0);
//      m_iPartCount = 0;
//      m_iED2KPartCount = 0;
//      m_iED2KPartHashCount = 0;
//      return;
//  }
//  // nr. of data parts
//  ASSERT( (uint64)(((uint64)nFileSize + (PARTSIZE - 1)) / PARTSIZE) <= (UINT)USHRT_MAX );
//  //PARTSIZE 9728000
//  //USHRT_MAX 65535
//  m_iPartCount = (uint16)(((uint64)nFileSize + (PARTSIZE - 1)) / PARTSIZE);
//  // nr. of parts to be used with OP_FILESTATUS
//  m_iED2KPartCount = (uint16)((uint64)nFileSize / PARTSIZE + 1);
//  // nr. of parts to be used with OP_HASHSETANSWER
//  m_iED2KPartHashCount = (uint16)((uint64)nFileSize / PARTSIZE);
//  if (m_iED2KPartHashCount != 0)
//      m_iED2KPartHashCount += 1;
//}
//代码分析
//Ed2K链接 如下
//ed2k://|file|100M.rar|142773857|54703D1BA90B7E8FB588C8137AD67A42|p=8DF50FD599BC060A943D464D10FD978B:EB2922FEFDA86F5DAE9EA8092EEF8D90:1B89E953F71BA8C9FCB079210BC62367|/
//这里面包含Data parts ,ED2K parts ,ED2K part hashs,与SetFileSiz函数中的的对应关系如下
//Data parts是m_iPartCount
//ED2K parts 是m_iED2KPartCount
//ED2K part hashs是m_iED2KPartHashCount
//Data parts的计算方法
//文件大小小于等于9.28M,Data parts就是1
//文件大小大于,以9.28M为单位,分割文件,用文件大小除以9.28M,得到一个数n,,如果有余数,那么Data parts就是n+1,没有余数Data parts就是n.
//代码如下,计算Data parts的代码设计的很巧妙.,.
//m_iPartCount = (uint16)(((uint64)nFileSize + (PARTSIZE - 1)) / PARTSIZE);
//ED2K parts的计算方法
//用文件大小去除以9.28M得到整数值然后加1
//代码如下
//m_iED2KPartCount = (uint16)((uint64)nFileSize / PARTSIZE + 1);
//ED2K part hashs的计算方法
//P=后面的字符串就 ED2K part hashs 也就是片段哈希值.例子中一共有三块.,每一块的Hash值之间用:号隔开
//如果文件大小小于9.28M,将不会出现"P="的字符串,ED2K part hashs为空
//如果文件大小大于等于9.28M,ED2K part 块数的计算方法是文件大小去除以9.28M得到整数值然后+1
//代码就是
//m_iED2KPartHashCount = (uint16)((uint64)nFileSize / PARTSIZE);
//if (m_iED2KPartHashCount != 0)
//m_iED2KPartHashCount += 1;
class CAddFileThread : public CWinThread
//添加文件线程
//针对下载的Part文件就没必要重头完整计算hashlist 和 整颗 AICH Hash Tree了,这样就加快了下载完成时候的hash计算。
class CAddFriend : public CDialog
//添加好友对话框
class CAddSourceDlg : public CResizableDialog
//添加资源对话框 使用了第三方库ResizableLib

emule中的分块处理和恢复机制,分块处理以及hash计算相关的类有:

CAICHHash、CAICHHashAlgo、CAICHHashTree、CAICHRecoveryHashSet、CAICHRequestedData、CAICHSyncThread、CAICHUntrustedHash等

class CArchivePreviewDlg : public CResizablePage
//存档预览对话框
class CArchiveRecovery
//提供一个自动处理Zip和rar的类
class CAsyncProxySocketLayer : public CAsyncSocketExLayer
//主要是提供了对SOCKSv4,SOCKSv5和HTTP1.1 代理的支持。
class CAsyncSocketEx : public CObject
//兼容CAsyncSocketEx类,把应用程序中所有的CAsyncSocket换成CAsyncSocketEx
//程序仍然能够和原来的功能相同,因此在使用上更加方便
//在消息分发机制上,它处理和Socket相关的消息的效率要比原始的MFC的CAsyncSocket类更高
//它支持通过实现CAsyncSocketExLayer类的方式,将一个Socket分成若干个层,从而可以很方便
//得实现许多网络功能,如:设置代理、使用SSL进行加密
class CAsyncSocketExHelperWindow
//当socket事件accept, read, write等发生时,发送消息到CAsyncSocketExHelperWindow中的窗口hWnd,
//然后CAsyncSocketExHelperWindow再通过回调函数WindowProc将消息发回到负责处理这个消息的CAsyncSocketEx上。
class CAsyncSocketExLayer
//异步通信程序库
//通过实现CAsyncSocketExLayer类的方式,将一个SOCKET分成若干个层,
//从而可以很方便得实现许多网络功能,如设置代理,或者是使用SSL进行加密等。
class CBarShader
//自定义条横着色器
class CBase64Coding
//基于64的编码
class CBitmapDataObject : public CCmdTarget
//图片数据对象
class CButtonST : public CButton
//自绘按钮控件
class CButtonsTabCtrl : public CTabCtrl
//自绘多标签控件
class CCaptchaGenerator
//产生验证码
class CCBBRecord
//BB信息记录 例如ip地址 开始位置 结束位置
class CChatItem
//任务节点
class CChatSelector : public CClosableTabCtrl, CFriendConnectionListener
//任务的管理
class CChatWnd : public CResizableDialog
//任务的设置对话框
class CCKey : public CObject
//如果哈希值存储某处其他(并保持有效的,只要这个对象存在)
class CClientCredits
//信誉机制的信息需要有一定的可靠性,在emule中采用了数字签名的方法来做到这一点
//Crypto++库为emule全程提供和数字签名验证相关的功能//struct CreditStruct{
//  uchar       abyKey[16];
//  uint32      nUploadedLo;    // uploaded TO him
//  uint32      nDownloadedLo;  // downloaded from him
//  uint32      nLastSeen;
//  uint32      nUploadedHi;    // upload high 32
//  uint32      nDownloadedHi;  // download high 32
//  uint16      nReserved3;
//  uint8       nKeySize;
//  uchar       abySecureIdent[MAXPUBKEYSIZE];
//};
//使用该结构来记录信息,如:上传量和下载量等
class CClientCreditsList
//提供了loadlist和savelist方法永久保存信誉相关的信息
//在创建时,会装载自己的公钥私钥,如果没有的话,会创建一对
//该类中包含的有效的信息都是经过其他人数字签名的,所以更加有信服力
class CClientDetailDialog : public CListViewWalkerPropertySheet
//客户详细信息显示对话框
class CClientReqSocket : public CEMSocket
//能够自动完成emule的packet识别工作,它有ProcessPacket和ProcessExtPacket来处理客户端和客户端之间的包
//其中前者是经典的eDonkey协议的包,后者是emule扩展协议的包
//表示了一个客户端的信息,侧重在网络数据方面,即负责两边的互相通信
class CClientUDPSocket : public CAsyncSocket, public CEncryptedDatagramSocket, public ThrottledControlSocket // ZZ:UploadBandWithThrottler (UDP)
//一个客户端UDP套接字处理
class CClientVersionInfo
//版本信息
class CCollection
//消息集合操作 例如:从文件中读取消息、把消息放到文件中、移除消息等等
class CCriticalSectionWrapper
//触发机制
class CDeadSource : public CObject
//地址源出错
class CDeadSourceList
//管理出错资源
class CDownloadQueue
//下载队列类,这个队列中的项目是CPartFile指针,它还需要能够提供对这个列表中的元素进行增加、查询、删除的功能(文件的hashID或索引)
//还要完成一些统计的工作,统计的信息都是放在对应的.part文件中
//因此该类进行初始化的时候,它需要寻找所有可能的下载路径,从那些路径中找到所有的.part文件,并且试图
//用这些文件来生成CPartFile类,并且将这些通过.part文件正确生成CPartFile类添加到自己的列表中
//在退出时,所有的下载任务的元信息也是自行保存,不会合成一个文件。//把它的列表中的CPartFile类中的Process方法都调用一遍
//下载情况的统计信息也是在每一轮的Process后进行更新的
//从这里看该方法在emule中是很有意义的,就是必须通过它来完成日常工作
//而且所有的这些process方法肯定是顺序执行,因此可以减少很多线程同步之类的问题
//void CDownloadQueue::Process(){
//
//  ProcessLocalRequests(); // send src requests to local server
//
//  uint32 downspeed = 0;
//  uint64 maxDownload = thePrefs.GetMaxDownloadInBytesPerSec(true);
//  if (maxDownload != UNLIMITED*1024 && datarate > 1500){
//      downspeed = (UINT)((maxDownload*100)/(datarate+1));
//      if (downspeed < 50)
//          downspeed = 50;
//      else if (downspeed > 200)
//          downspeed = 200;
//  }
//
//  while(avarage_dr_list.GetCount()>0 && (GetTickCount() - avarage_dr_list.GetHead().timestamp > 10*1000) )
//      m_datarateMS-=avarage_dr_list.RemoveHead().datalen;
//
//  if (avarage_dr_list.GetCount()>1){
//      datarate = (UINT)(m_datarateMS / avarage_dr_list.GetCount());
//  } else {
//      datarate = 0;
//  }
//
//  uint32 datarateX=0;
//  udcounter++;
//
//  theStats.m_fGlobalDone = 0;
//  theStats.m_fGlobalSize = 0;
//  theStats.m_dwOverallStatus=0;
//  //filelist is already sorted by prio, therefore I removed all the extra loops..
//  for (POSITION pos = filelist.GetHeadPosition();pos != 0;){
//      CPartFile* cur_file = filelist.GetNext(pos);
//
//      // maintain global download stats
//      theStats.m_fGlobalDone += (uint64)cur_file->GetCompletedSize();
//      theStats.m_fGlobalSize += (uint64)cur_file->GetFileSize();
//
//      if (cur_file->GetTransferringSrcCount()>0)
//          theStats.m_dwOverallStatus  |= STATE_DOWNLOADING;
//      if (cur_file->GetStatus()==PS_ERROR)
//          theStats.m_dwOverallStatus  |= STATE_ERROROUS;
//
//
//      if (cur_file->GetStatus() == PS_READY || cur_file->GetStatus() == PS_EMPTY){
//          datarateX += cur_file->Process(downspeed, udcounter);
//      }
//      else{
//          //This will make sure we don't keep old sources to paused and stoped files..
//          cur_file->StopPausedFile();
//      }
//  }
//
//  TransferredData newitem = {datarateX, ::GetTickCount()};
//  avarage_dr_list.AddTail(newitem);
//  m_datarateMS+=datarateX;
//
//  if (udcounter == 5){
//      if (theApp.serverconnect->IsUDPSocketAvailable()){
//          if((!lastudpstattime) || (::GetTickCount() - lastudpstattime) > UDPSERVERSTATTIME){
//              lastudpstattime = ::GetTickCount();
//              theApp.serverlist->ServerStats();
//          }
//      }
//  }
//
//  if (udcounter == 10){
//      udcounter = 0;
//      if (theApp.serverconnect->IsUDPSocketAvailable()){
//          if ((!lastudpsearchtime) || (::GetTickCount() - lastudpsearchtime) > UDPSERVERREASKTIME)
//              SendNextUDPPacket();
//      }
//  }
//
//  CheckDiskspaceTimed();
//
//  // ZZ:DownloadManager -->
//  if((!m_dwLastA4AFtime) || (::GetTickCount() - m_dwLastA4AFtime) > MIN2MS(8)) {
//      theApp.clientlist->ProcessA4AFClients();
//      m_dwLastA4AFtime = ::GetTickCount();
//  }
//  // <-- ZZ:DownloadManager
//}
class CED2KFileLink : public CED2KLink
//ED2K文件链接操作
class CED2KFileTypes
//ED2K文件类型
class CED2KLink
//ED2K连接操作
class CED2kLinkDlg : public CResizablePage
ED2K链接操作对话框
class CED2KNodesListLink : public CED2KLink
ED2K节点列表链接操作
class CED2KSearchLink : public CED2KLinkED2K搜索链接
class CED2KServerLink : public CED2KLink
//ED2K服务器链接
class CED2KServerLink : public CED2KLink
//ED2K服务器链接
class CEMFileSize
//操作EM文件
class CEMSocket : public CEncryptedStreamSocket, public ThrottledFileSocket // ZZ:UploadBandWithThrottler (UDP)
//分离出状态,如当前是否在发送控制信息等,它的SendControlData方法和UploadBandwidthThrottler进行配合进行全局的限速功能
//如果要打到上传数据限速的目的,不应该直接调用标准的Send或SendTo方法,而是调用SendPacket
//Packet是一个结构体,包含了一个emule协议中完整的包,还内置了PackPacket和UnPackPacket方法,可以自行进行压缩和解压的功能//开发发起连接,先检查是否设置了代理
//  BOOL CEMSocket::Connect(SOCKADDR* pSockAddr, int iSockAddrLen)
//{
//  InitProxySupport();
//  return CEncryptedStreamSocket::Connect(pSockAddr, iSockAddrLen);
//}//成员函数
//virtual SocketSentBytes SendControlData(uint32 maxNumberOfBytesToSend, uint32 minFragSize)
//{ return Send(maxNumberOfBytesToSend, minFragSize, true); };
//virtual SocketSentBytes SendFileAndControlData(uint32 maxNumberOfBytesToSend, uint32 minFragSize)
//{ return Send(maxNumberOfBytesToSend, minFragSize, false); };
//两个方法都调用了Send方法,该两个方法是在UploadBandwidthThrottler的工作线程中的大环境中被调用的
class CemuleApp : public CWinApp//通过在注册表里添加一些项目可以让一个程序和某种链接或者某个后缀的文件产生关联
//  bool CemuleApp::ProcessCommandline()
//{
//  bool bIgnoreRunningInstances = (GetProfileInt(_T("eMule"), _T("IgnoreInstances"), 0) != 0);
//  for (int i = 1; i < __argc; i++){
//      LPCTSTR pszParam = __targv[i];
//      if (pszParam[0] == _T('-') || pszParam[0] == _T('/')){
//          pszParam++;
//#ifdef _DEBUG
//          if (_tcsicmp(pszParam, _T("assertfile")) == 0)
//              _CrtSetReportHook(CrtDebugReportCB);
//#endif
//          if (_tcsicmp(pszParam, _T("ignoreinstances")) == 0)
//              bIgnoreRunningInstances = true;
//
//          if (_tcsicmp(pszParam, _T("AutoStart")) == 0)
//              m_bAutoStart = true;
//      }
//  }
//
//  CCommandLineInfo cmdInfo;
//  ParseCommandLine(cmdInfo);
//
//  // 如果我们创建了我们的TCP侦听套接字SO_REUSEADDR,我们必须确保有2 eMule总是使用相同的端口。
//  // NOTE: 这不会阻止从其他一些使用该端口的应用程序!
//  UINT uTcpPort = GetProfileInt(_T("eMule"), _T("Port"), DEFAULT_TCP_PORT_OLD);
//  CString strMutextName;
//  strMutextName.Format(_T("%s:%u"), EMULE_GUID, uTcpPort);
//  m_hMutexOneInstance = ::CreateMutex(NULL, FALSE, strMutextName);
//
//  HWND maininst = NULL;
//  bool bAlreadyRunning = false;
//
//  //启动另外一个实例
//  if(bIgnoreRunningInstances)
//  {
//      if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen
//          && (cmdInfo.m_strFileName.Find(_T("://")) > 0
//          || CCollection::HasCollectionExtention(cmdInfo.m_strFileName)) )
//          bIgnoreRunningInstances = false;
//  }
//  if (!bIgnoreRunningInstances){
//      bAlreadyRunning = (::GetLastError() == ERROR_ALREADY_EXISTS ||::GetLastError() == ERROR_ACCESS_DENIED);
//      if (bAlreadyRunning) EnumWindows(SearchEmuleWindow, (LPARAM)&maininst);
//  }
//
//  if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen) {
//      CString* command = new CString(cmdInfo.m_strFileName);
//      if (command->Find(_T("://"))>0) {
//          sendstruct.cbData = (command->GetLength() + 1)*sizeof(TCHAR);
//          sendstruct.dwData = OP_ED2KLINK;
//          sendstruct.lpData = const_cast<LPTSTR>((LPCTSTR)*command);
//          if (maininst){
//              SendMessage(maininst, WM_COPYDATA, (WPARAM)0, (LPARAM)(PCOPYDATASTRUCT)&sendstruct);
//              delete command;
//              return true;
//          }
//          else
//              pstrPendingLink = command;
//      }
//      else if (CCollection::HasCollectionExtention(*command)) {
//          sendstruct.cbData = (command->GetLength() + 1)*sizeof(TCHAR);
//          sendstruct.dwData = OP_COLLECTION;
//          sendstruct.lpData = const_cast<LPTSTR>((LPCTSTR)*command);
//          if (maininst){
//              SendMessage(maininst, WM_COPYDATA, (WPARAM)0, (LPARAM)(PCOPYDATASTRUCT)&sendstruct);
//              delete command;
//              return true;
//          }
//          else
//              pstrPendingLink = command;
//      }
//      else {
//          sendstruct.cbData = (command->GetLength() + 1)*sizeof(TCHAR);
//          sendstruct.dwData = OP_CLCOMMAND;
//          sendstruct.lpData = const_cast<LPTSTR>((LPCTSTR)*command);
//          if (maininst){
//              SendMessage(maininst, WM_COPYDATA, (WPARAM)0, (LPARAM)(PCOPYDATASTRUCT)&sendstruct);
//              delete command;
//              return true;
//          }
//          // 不要启动,如果我们调用的“退出”命令。
//          if (command->CompareNoCase(_T("exit")) == 0) {
//              delete command;
//              return true;
//          }
//          delete command;
//      }
//  }
//  return (maininst || bAlreadyRunning);
//}//theApp.serverlist->Init();
//从文件中读取ServerMet_Struct( 标签)和从文本文件读取静态server列表;//下载队列初始化
//theApp.downloadqueue->Init();//启动客户端监听
//theApp.listensocket->StartListening()
//
//对象创建套接字
//CClientUDPSocket theApp.clientudp->Create()
//
//检测启动是否自动连接服务器
//if (thePrefs.DoAutoConnect())
//连接emule服务器
//theApp.emuledlg->OnBnClickedButton2();
//调用connectserver对象的trytoconnectanyserver()连接全局服务器。
//主程序对话框
class CemuleDlg : public CTrayDialog//关键启动(主连接)
//  void CemuleDlg::StartConnection()
//{
//  if (   (!theApp.serverconnect->IsConnecting() && !theApp.serverconnect->IsConnected())
//      || !Kademlia::CKademlia::IsRunning())
//  {
//      //UPnP是仍然在试图打开的端口。
//      //为了不连接的服务器/ Kad网络前的端口都开了,我们拖延的连接,
//      //直到UPnP得到一个低-ID或达到超时
//      if (m_hUPnPTimeOutTimer != 0 && !m_bConnectRequestDelayedForUPnP){
//          AddLogLine(false, GetResString(IDS_DELAYEDBYUPNP));
//          AddLogLine(true, GetResString(IDS_DELAYEDBYUPNP2));
//          m_bConnectRequestDelayedForUPnP = true;
//          return;
//      }
//      else{
//          m_bConnectRequestDelayedForUPnP = false;
//          if (m_hUPnPTimeOutTimer != 0){
//              VERIFY( ::KillTimer(NULL, m_hUPnPTimeOutTimer) );
//              m_hUPnPTimeOutTimer = 0;
//          }
//          AddLogLine(true, GetResString(IDS_CONNECTING));
//
//          // ed2k
//          if ((thePrefs.GetNetworkED2K() || m_bEd2kSuspendDisconnect) && !theApp.serverconnect->IsConnecting() && !theApp.serverconnect->IsConnected()) {
//              theApp.serverconnect->ConnectToAnyServer();
//          }
//
//          // kad
//          if ((thePrefs.GetNetworkKademlia() || m_bKadSuspendDisconnect) && !Kademlia::CKademlia::IsRunning()) {
//              Kademlia::CKademlia::Start();
//          }
//      }
//
//      ShowConnectionState();
//  }
//  m_bEd2kSuspendDisconnect = false;
//  m_bKadSuspendDisconnect = false;
//}
//
断开连接(主连接)
//void CemuleDlg::CloseConnection()
//{
//  if (theApp.serverconnect->IsConnected()){
//      theApp.serverconnect->Disconnect();
//  }
//
//  if (theApp.serverconnect->IsConnecting()){
//      theApp.serverconnect->StopConnectionTry();
//  }
//  Kademlia::CKademlia::Stop();
//  theApp.OnlineSig(); // Added By Bouc7
//  ShowConnectionState();
//}//设置启动定时器
//CEmuleDlg::OnInitDlg ::SetTimer(NULL, NULL, 300, StartupTimer) ;
//定时器函数  完成各对象初始化初始化服务器列表
//static void CALLBACK StartupTimer(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime);
class CEnBitmap : public CBitmap
//图片操作
class CEncryptedDatagramSocket
//数据包编码
class CEncryptedStreamSocket : public CAsyncSocketEx
//数据流编码
//操作文件
class CFileDataIO
//数据操作的行为或数据操作的对象分割开来
//各种整形、字符串以及Tag类型,整形读写起来比较简单
class CFileDetailDialog : public CListViewWalkerPropertySheet
//文件详细显示对话框
class CFirewallOpener
//防火墙操作 例如:打开某端口、删除策略等
class CFriend : public Kademlia::CKadClientSearcher
//Kad网邻居类
class CFriendConnectionListener//针对Kad网邻居的连接监听
class CFriendList
//Kad网邻居列表
class CFriendListCtrl : public CMuleListCtrl
//Kad网邻居列表管理
class CGDIThread : public CWinThread
//界面绘图操作线程
class CGetMediaInfoThread : public CWinThread
//得到媒体文件信息的线程
class CGetMediaInfoThread : public CWinThread
//得到媒体文件信息的线程
class CHttpClientDownSocket : public CHttpClientReqSocket
//http下载套接字
class CHttpClientReqSocket : public CClientReqSocket
//扩展了CClientReqSocket
class CHttpDownloadDlg : public CDialog
//下载任务显示对话框
class CHyperLink
//超链接
class CHyperTextCtrl : public CWnd
//超链接文本
class CIPFilter
//IP地址过滤器,通过识别各种类型的ip地址过滤信息
//它能够把不希望连接的网络地址过滤掉
//emule中所有需要连接网络的地方使用的都是统一的过滤数据
class CIPFilterDlg : public CResizableDialog
//ip过滤显示对话框
//专注某个特定文件的信息的类(增加信息存取)
class CKnownFile : public CShareableFile
//把读到的文件信息都保存成一个一个的tag
//它在运行中会尽量获得更多的文件信息
class CKnownFileList //使用了MFC的CMap类来维护内部的hash表,它内部维护了一个已知的文件的列表和取消了文件列表,//hash表的关键字都是文件hash的值,能够判断出文件名不同而内容相同的文件
class CPartFile : public CKnownFile
//是emule中用来表示一个下载任务的类(一个还没有下载完成的文件)
//当下载任务时emule会在下载目录中创建两个文件,以三位数字家后缀part的文件,表示的是对应文件的元信息
//part文件会创建得和原始文件大小一样,当下载完成后,文件名会修改成它本来的名称
//事实上,诸如:文件名字、修改日期、大小、下载完成的信息等信息元素都在对应的.part元文件中//struct Gap_Struct
//{
//  uint64 start;
//  uint64 end;
//};
//该结构表示一个吭,说明该文件从多少字节的偏移到多少字节偏移是一个吭
//变量成员gaplist说明该文件目前的吭的状况列表
//需要注意的是有时填了吭的中间部分后,会把一个坑编程两个吭,吭的列表也会被存进.part.met中//该类的创建有几种可能,从搜索文件中创建(点击下载)、从一个包含了ed2k链接的字符串中创建、emule重启恢复以前的下载任务创建。
//这时就是去下载目录中寻找那些.part文件了,另外它还需要不断得处理下载到的数据,为了减少磁盘开销,
//使用了Requested_Block_Struct结构来暂存写入的数据
//它内部维护一个CupDownClient的列表,如果知道该文件的一个新的来源信息,就会创建一个对应的CUpDownClient
//它还要把它的状态用彩色的条状物显示出来(GUI)。
class CPartFileConvert
//能偶对其它版本的emule下载的文件进行转换
class CPeerCacheFinder
//为前面的PeerCache技术的主控类 由Joltid公司开发的技术,它可以允许你从ISP提供的一些快照服务器上快速得上传或下载一些文件
这技术的好处是可以减少骨干网络的带宽消耗,将部分本来需要在骨干网上走的流量转移到ISP的内部
class CPreferences//掌握着程序的大部分配置数据,它们的特点都是有很多的成员变量,而且还是静态的,这种方式可以保证他们的唯一性,并且把这些//变量统一到一个类管理。但是实际上并不需要了解每个变量的含义//thePrefs和theStats是它的唯一的实例!~~
class CRARFile
//操作rar文件
class CScheduler
//能够实现下载任务的定时下载
class CSearchFile : public CAbstractFile
//保存了某个文件和搜索相关的信息,不是这个文件本身的信息,就是都在哪些机器上有这个文件
//以及哪个服务器上搜索到这个文件,甚至可以向搜索文件添加预览//阙套结构体
//struct SServer {
//  SServer() {
//      m_nIP = 0;
//      m_nPort = 0;
//      m_uAvail = 0;
//      m_bUDPAnswer = false;
//  }
//  SServer(uint32 nIP, uint16 nPort, bool bUDPAnswer) {
//      m_nIP = nIP;
//      m_nPort = nPort;
//      m_uAvail = 0;
//      m_bUDPAnswer = bUDPAnswer;
//  }
//  friend __inline bool __stdcall operator==(const CSearchFile::SServer& s1, const CSearchFile::SServer& s2) {
//      return s1.m_nIP==s2.m_nIP && s1.m_nPort==s2.m_nPort;
//  }
//  uint32 m_nIP;
//  uint16 m_nPort;
//  UINT   m_uAvail;
//  bool   m_bUDPAnswer;
//};
//struct SClient {
//  SClient() {
//      m_nIP = 0;
//      m_nPort = 0;
//      m_nServerIP = 0;
//      m_nServerPort = 0;
//  }
//  SClient(uint32 nIP, uint16 nPort, uint32 nServerIP, uint16 nServerPort) {
//      m_nIP = nIP;
//      m_nPort = nPort;
//      m_nServerIP = nServerIP;
//      m_nServerPort = nServerPort;
//  }
//  friend __inline bool __stdcall operator==(const CSearchFile::SClient& c1, const CSearchFile::SClient& c2) {
//      return c1.m_nIP==c2.m_nIP && c1.m_nPort==c2.m_nPort &&
//          c1.m_nServerIP==c2.m_nServerIP && c1.m_nServerPort==c2.m_nServerPort;
//  }
//  uint32 m_nIP;
//  uint32 m_nServerIP;
//  uint16 m_nPort;
//  uint16 m_nServerPort;
//};
//此两个结构体表示了该搜索文件的可能来源,服务器或者其它客户端
class CSearchList
//是emule中的搜索列表,掌握所有的搜索请求(CSearchFile是列表中的元素,代表一次搜索的相关信息)
//对外提供了搜索表达的接口,即每当有一个新的搜索提交时成员函数NewSearch会建立一个新的搜索项
//但是此时还没有任何对应的搜索文件,只是在文件个数和搜索id的对应表(m_foundFileCount和m_foundSourceCount)中建立新的项目。
//该类还负责和搜索有关的信息的储存和读取,本身并不进行搜索
class CServer
//服务器信息类
//ip地址 端口 以及属性的个数 ……
//自定义的连接服务器的类
class CServerConnect
//成员函数connectedsocket是CServerSocket类型(套接字)//成员保存若干CServerSocket类型的指针
//CMap<ULONG, ULONG, CServerSocket*, CServerSocket*> connectionattemps;
//只是可以同时试图连接到若干个服务器上//关键连接服务器(连接服务器的起点)
//  void CServerConnect::ConnectToServer(CServer* server, bool multiconnect, bool bNoCrypt)
//{
//  if (!multiconnect) {
//      StopConnectionTry();
//      Disconnect();
//  }
//  connecting = true;
//  singleconnecting = !multiconnect;
//  theApp.emuledlg->ShowConnectionState();
//
//  CServerSocket* newsocket = new CServerSocket(this, !multiconnect);
//  m_lstOpenSockets.AddTail((void*&)newsocket);
//  newsocket->Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_CLOSE | FD_CONNECT, thePrefs.GetBindAddrA());
//  newsocket->ConnectTo(server, bNoCrypt);
//  connectionattemps.SetAt(GetTickCount(), newsocket);
//}//成员函数
//  void    ConnectionEstablished(CServerSocket* sender);
//tcp连接建立后的第一个包的发送,即向服务器发出登陆信息
//如果登陆成功,则能够从服务器处获取自己的id(32位)
//自定义的连接服务器的类
class CServerConnect
//成员函数connectedsocket是CServerSocket类型(套接字)//成员保存若干CServerSocket类型的指针
//CMap<ULONG, ULONG, CServerSocket*, CServerSocket*> connectionattemps;
//只是可以同时试图连接到若干个服务器上//关键连接服务器(连接服务器的起点)
//  void CServerConnect::ConnectToServer(CServer* server, bool multiconnect, bool bNoCrypt)
//{
//  if (!multiconnect) {
//      StopConnectionTry();
//      Disconnect();
//  }
//  connecting = true;
//  singleconnecting = !multiconnect;
//  theApp.emuledlg->ShowConnectionState();
//
//  CServerSocket* newsocket = new CServerSocket(this, !multiconnect);
//  m_lstOpenSockets.AddTail((void*&)newsocket);
//  newsocket->Create(0, SOCK_STREAM, FD_READ | FD_WRITE | FD_CLOSE | FD_CONNECT, thePrefs.GetBindAddrA());
//  newsocket->ConnectTo(server, bNoCrypt);
//  connectionattemps.SetAt(GetTickCount(), newsocket);
//}//成员函数
//  void    ConnectionEstablished(CServerSocket* sender);
//tcp连接建立后的第一个包的发送,即向服务器发出登陆信息
//如果登陆成功,则能够从服务器处获取自己的id(32位)
class CServerSocket : public CEMSocket
//它比CEMSocket要多保存一些状态 比如:当前服务器连接状态、当前所有连接的服务器的信息//成员函数
//bool ProcessPacket(const BYTE* packet, uint32 size, uint8 opcode);
//直接把emule客户端和服务器之间的通信协议(服务器发回的包)
class CUDPSocket : public CAsyncSocket, public CEncryptedDatagramSocket, public ThrottledControlSocket // ZZ:UploadBandWithThrottler (UDP)
//UDP协议的包,因为UDP本来就是以一个包一个包作为单位在网络上流传的,不需要在包的内容中再包含表示长度的字段
//每个UDP包的第一字节是协议族代码,其它内容就是包的内容。
class CUpDownClient : public CObject
//表示了一个客户端的信息,即负责从逻辑上对网络另一边的一个客户端进行表达
//该类是emule中代码量最大的类
//BaseClient.cpp实现该类基本的各种状态信息的获取或设置,以及按照要求处理和发送处理请求
//逻辑实现和网络进行了区分,该类本身不从网络接收或者发送消息,它只是提供各种请求的处理接口,以及在发送请求时构造好相应的packet
//并交给自己对应的网络套接字发送出去
//DownloadClient.cpp中实现该类的,和下载相关的功能,它还包括了各种下载请求的发送以及响应的数据的接收
//UploadClient.cpp中实现该类的,和上传相关的功能,即接受进来的下载请求,并且生成响应的文件块发送出去。
class CUploadQueue
//上传队列类
//这个列表类中只有以CUpDownClient为元素的列表,它和其它列表类还有一个很大的不同,就是它所保存的信息都不需要持久化
//即不需要在当前的emule退出后还得记录自己正在给谁上传
//当收到一个新的下载请求后,它会把对应的客户端先添加到排队列表中,以后再根据情况把他们不断添加到上传列表中,在这里,信誉机制将会
//对此产生影响//添加和删除客户端的上传列表。这也使得确保所有的上传槽的插座总是有足够的数据包队列,etc.This方法被称为约为每100毫秒。
//向上传队列中的所有客户端移除发送数据,而排队的客户端是不会得到这个机会的
//它还需要完成关于上传方面的统计信息
//void CUploadQueue::Process() {
//
//  DWORD curTick = ::GetTickCount();
//
//  UpdateActiveClientsInfo(curTick);
//
//  if (ForceNewClient()){
//      //没有足够的开放上传
//      AddUpNextClient(_T("Not enough open upload slots for current ul speed"));
//  }
//
//  // 循环上传通道的数据。
//  POSITION pos = uploadinglist.GetHeadPosition();
//  while(pos != NULL){
//      // 获取客户端
//      CUpDownClient* cur_client = uploadinglist.GetNext(pos);
//      if (thePrefs.m_iDbgHeap >= 2)
//          ASSERT_VALID(cur_client);
//      //可以停留在上次上传的位置
//      if (!cur_client->socket)
//      {
//          RemoveFromUploadQueue(cur_client, _T("Uploading to client without socket? (CUploadQueue::Process)"));
//          if(cur_client->Disconnected(_T("CUploadQueue::Process"))){
//              delete cur_client;
//          }
//      } else {
//          cur_client->SendBlockData();
//      }
//  }
//
//  // 保存使用的带宽速度计算
//  uint64 sentBytes = theApp.uploadBandwidthThrottler->GetNumberOfSentBytesSinceLastCallAndReset();
//  avarage_dr_list.AddTail(sentBytes);
//  m_avarage_dr_sum += sentBytes;
//
//  (void)theApp.uploadBandwidthThrottler->GetNumberOfSentBytesOverheadSinceLastCallAndReset();
//
//  avarage_friend_dr_list.AddTail(theStats.sessionSentBytesToFriend);
//
//  //节省时间
//  avarage_tick_list.AddTail(curTick);
//
//  // don't save more than 30 secs of data
//  while(avarage_tick_list.GetCount() > 3 && !avarage_friend_dr_list.IsEmpty() && ::GetTickCount()-avarage_tick_list.GetHead() > 30*1000) {
//      m_avarage_dr_sum -= avarage_dr_list.RemoveHead();
//      avarage_friend_dr_list.RemoveHead();
//      avarage_tick_list.RemoveHead();
//  }
//
//  if (GetDatarate() > HIGHSPEED_UPLOADRATE_START && m_hHighSpeedUploadTimer == 0)
//      UseHighSpeedUploadTimer(true);
//  else if (GetDatarate() < HIGHSPEED_UPLOADRATE_END && m_hHighSpeedUploadTimer != 0)
//      UseHighSpeedUploadTimer(false);
//};
class CUrlClient : public CUpDownClient
//利用http协议对原有的emule协议进行包装,以便使它能够尽可能地穿越更多的网络的防火墙
class CWebServer
//能够在本地打开一个web服务器,然后通过浏览器来控制你的emule
class Packet
//emule的通信协议的最小单位,例如:一个头部信息的缓冲区、指定协议簇代码等
//它内部实现了压缩和解压的方法,该方法直接调用Zlib库中的压缩方法,可以减少数据的传输量
//这里要注意的一点的就是压缩的时候协议簇代码是不参与压缩的,压缩完毕后会更改协议簇代码
class ThrottledControlSocket
//任何其它的网络套接字类如果想实现限速的功能,只需要在
//其默认的发送函数(Send或Sendto)中不发送数据而是把数据缓冲起来
//然后在实现接口SendFileAndControlData或SendControlData方法时才真正把数据发送出去
class UploadBandwidthThrottler :public CWinThread
//一个CWinThread的子类,平时单独运行一个线程,控制全局的上传速度的
//在RunInteral中计算本次分配额(能够发送多少字节)、计算本次应该睡眠多少时间(限速)
//操作控制信息队列,发送该队列中的数据
//注意,该控制队列中的套接字(m_ControlQueueFirst_list和m_ControlQueue_list)只使用一次就离开队列
//而标准队列中的套接字不会这样,在一轮循环结束后如果还有没有用完的发送数据的配额,则会有部分配额保存到下一轮
//在标准队列m_StandardOrder_list里面排队的都是实现了ThrottledFileSocket接口的类,通过这些类能够传输文件的内容和控制信息//把要添加到队列的套接字全部添加到两个临时队列 然后根据它们的优先级添加到普通的临时队列
//UploadBandwidthThrottler使用了两个临界区、两个事件来暂停整个循环和线程
//void UploadBandwidthThrottler::QueueForSendingControlPacket(ThrottledControlSocket* socket, bool hasSent) {
//  // Get critical section
//  tempQueueLocker.Lock();
//
//  if(doRun) {
//      if(hasSent) {
//          m_TempControlQueueFirst_list.AddTail(socket);
//      } else {
//          m_TempControlQueue_list.AddTail(socket);
//      }
//  }
//
//  // End critical section
//  tempQueueLocker.Unlock();
//}

源码较多,学习难度大,没那么多时间咬代嚼码!难度大的不仅仅是代码多、复杂,更重要的是它的协议。如果你完全明白了,那你就是牛人,像牛一样辛苦的人,o(∩_∩)o 哈哈!~

学习的目标是成熟!~~~~

开源项目之电驴emule相关推荐

  1. 电驴emule v0.50a安装与设置

    电驴emule v0.50a安装与设置 下载地址: http://www.emule-project.net/ Kad节点文件 nodes.dat http://emulefans.com/emule ...

  2. 电驴 emule 源码分析 (1)

    关于电驴emule 的源码,网上有一个  叫刘刚的人 分析的 很多,但是如果你只是看别人的分析,自己没有亲身去阅读代码的话,恐怕很难  剖析整个系统. 关于emule  主要就是 连接 kad网络部分 ...

  3. 使电驴Emule获得HighID的Dlink 路由设定的解决方法

    1.查看本机局域网IP 2.查看电驴Emule的TCP和UDP的端口 3.设置DLink路由 高级->端口转发,然后进行如下设置: 4.重启路由,解决

  4. 如何搜索的时候去除电驴emule的乱码显示

    首先 打开emule安装目录 找到confiq文件夹 打开 找到 wordfilter文件--------是一个txt文件 打开 把里面的东西全部删除-- 关闭保存文件 最后 重起emule 好啦-- ...

  5. 让电驴(Emule)获取更高下载速度的方法 !(电驴下载慢的朋友可以进来学习下)

    原文地址::http://bbs.guitarchina.com/thread-305056-1-1.html 很多朋友抱怨电驴下载速度太慢(我也在其中),所以小弟昨天特别去找了很多文章来研究后设置了 ...

  6. 每日更新的电驴 eMule/eDonkey 服务器列表

    每日更新的电驴服务器列表,在这里留个记录,省的每次都要重新找 List如下: 1. http://ed2k.2x4u.de/index.html 或者 http://ed2k.has.it 2. ht ...

  7. GitHub开源项目学习 电商系统Mall (三) SpringBoot+MyBatis搭建基本骨架

    mysql数据库环境搭建 下载并安装mysql5.7版本 设置数据库账号密码 创建数据库mall 导入Mall数据库脚本 https://github.com/macrozheng/mall-lear ...

  8. GitHub开源项目学习 电商系统Mall (四) mall整合SpringSecurity和JWT实现认证和授权(一)

    mall整合SpringSecurity和JWT实现认证和授权(一) https://github.com/macrozheng/mall 跳过了官方Learning中较简单的Swagger-UI的实 ...

  9. GitHub开源项目学习 电商系统Mall (二) Mac搭建Mall前后台环境

    Mac搭建Mall前后台环境 Docker环境安装 此处不赘述,本机安装docker ce version 19.03 https://www.runoob.com/docker/macos-dock ...

最新文章

  1. R语言因子分析FA(factor analysis)步骤实战
  2. hive的multi-distinct可能带来性能恶化
  3. VS(Visual Studio)中快速找出含中文的字符串
  4. 实验二:网络嗅探与欺骗
  5. 【大话设计模式】设计模式系统学习大合集
  6. JS OOP -02 深入认识JS中的函数
  7. 《MySQL 8.0.22执行器源码分析(2)解读函数 ExecuteIteratorQuery》
  8. 【英语学习】【WOTD】billion 释义/词源/示例
  9. jmeter测试工具应用场景【测试帮日记公开课】
  10. kafka_2.10-0.8.1.1.tgz的1或3节点集群的下载、安装和配置(图文详细教程)绝对干货...
  11. ai跟随路径_怎么在ai中创建文本路径?Ai中怎样沿路径创建文本?
  12. 在论文中如何设置页眉页脚
  13. 如何通过数据驱动业务发展
  14. win10应用商店恢复
  15. Mac用自带软件QuickTime Player进行录屏
  16. 深入探索Android卡顿优化(下)
  17. 【Cocos Creator 游戏开发】开发日志-前言
  18. 目标跟踪(1)基于OpenCV实现单目标跟踪
  19. 基于Java的SMTP协议邮件发送模拟系统
  20. 计算星期几(蔡勒公式)

热门文章

  1. 杭州夫妻“最牛散户” 一年交易700亿元
  2. 主流nosql数据库对比
  3. java基础巩固-宇宙第一AiYWM:为了维持生计,Spring全家桶_Part1-5(学学Spring源码呗:BeanFactory与ApplicationContext的暗潮涌动与争风吃醋)~整起
  4. 低通滤波器和高通滤波器的程序实现原理推导
  5. java mail 收 附件_使用 JavaMail 收发邮件,解决中文附件问题
  6. uni-app返回上一层页面后数据刷新重置解决办法,uni-app页面不刷新,强制刷新页面方法!
  7. FPGA驱动旋转编码器(Verilog)
  8. Jsp实现Javaweb页面
  9. 【每天play】为了学好python需要从脚下做起,Linux基础-用户管理 P70-80
  10. Springboot上传文件时提示405