开源项目之电驴emule
一款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 //添加資源對話框 使用了第三方庫ResizableLibemule中的分塊處理和恢復機制,分塊處理以及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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS 6.3下Samba服务器的
- 下一篇: 12. tie_breaker的使用原因