emule中节点加入Kad网络过程(源代码详解)【对原文部分改进】

from: http://blog.csdn.net/chenbuaa/article/details/2301656


emule中节点加入Kad网络过程(源代码详解)

程序启动:

EmuleDlg.cpp中函数BOOL CemuleDlg::OnInitDialog(),此函数用于对话框的初始化,在这个函数里添加了定时器:VERIFY( (m_hTimer = ::SetTimer(NULL, NULL, 300, StartupTimer)) != NULL );

在这里添加了函数void CALLBACK CemuleDlg::StartupTimer(HWND /*hwnd*/, UINT /*uiMsg*/, UINT /*idEvent*/, DWORD /*dwTime*/),

case 2:

theApp.Kad_Dlg->status++;

if(!theApp.listensocket->StartListening())

ASSERT(0);

if(!theApp.clientudp->Create())

ASSERT(0);

theApp.Kad_Dlg->status++;

break;



[PS: 现在已经不是这样了,没有了Kad_Dlg, 在cemuleDlg.cpp的2087行调用了Kad的Start()函数]


在StartupTimer这个函数里,添加了一个ListenSocket的侦听端,并且在本地节点创建了一个CClientUDPSocket* clientudp;

然后程序启动。

顺便说一句,在CEmule类中定义了许多的类的实例,这都在今后使用到:

UploadBandwidthThrottler* uploadBandwidthThrottler;

CClientList* clientlist;

CClientUDPSocket* clientudp;

CListenSocket* listensocket;

CSharedFileList* sharedfiles;

CDownloadQueue* downloadqueue;

CUploadQueue* uploadqueue;

CServerList* serverlist;

LastCommonRouteFinder* lastCommonRouteFinder;

CServerConnect* serverconnect;

CIPFilter* ipfilter;

CClientCreditsList* clientcredits;

CSearchList* searchlist;

CKnownFileList* knownfiles;

CMMServer* mmserver;

AppState m_app_state; // defines application state for shutdown

CMutex hashing_mut;

CString m_strCurVersionLong;

CPeerCacheFinder* m_pPeerCache;

CFriendList* friendlist;

CFirewallOpener* m_pFirewallOpener;//hyper added

节点加入网络:


[emuledlg.cpp的:2087行 ] 

Emule连接Kad网络时,调用函数:Kademlia::CKademlia::Start(); Start()这个函数没有做什么实际意义上的事情,主要是new了几个类:

m_pInstance = new CKademlia();

m_pInstance->m_pPrefs = pPrefs;

m_pInstance->m_pUDPListener = NULL;

m_pInstance->m_pRoutingZone = NULL;

m_pInstance->m_pIndexed = new CIndexed();

m_pInstance->m_pRoutingZone = new CRoutingZone();

m_pInstance->m_pUDPListener = new CKademliaUDPListener();

并且更改了几个定时器的时间。

接着程序转入到routingzone.cpp中执行

在上面那部分的Start ()函数体内部初始化了CRoutingZone这个类,这个类的构造函数CRoutingZone::CRoutingZone()体中调用函数 Init(NULL, 0, CUInt128((ULONG)0));来初始化根节点(应该就是本地节点)。 


              // Can only create routing zone after prefs
              // Set our KadID for creating the contact tree
              CKademlia  :: GetPrefs  ()->  GetKadID (&  uMe  );
              m_sFilename  =  szFilename  ;
              // Init our root node.
              Init  ( NULL  , 0,  CUInt128 ((  ULONG  )0));


在void CRoutingZone::Init(CRoutingZone *pSuper_zone, int iLevel, const CUInt128 &uZone_index)函数体内部创建了一个新的m_pBin = new CRoutingBin();


              // Init all Zone vars
              // Set this zones parent
              m_pSuperZone  =  pSuper_zone  ;
              // Set this zones level
              m_uLevel  =  iLevel  ;
              // Set this zones CUInt128 Index
              m_uZoneIndex  =  uZone_index  ;
              // Mark this zone has having now leafs.
              m_pSubZones  [0] =  NULL  ;
              m_pSubZones  [1] =  NULL  ;
              // Create a new contact bin as this is a leaf.
              m_pBin  =  new  CRoutingBin();

              // Set timer so that zones closer to the root are processed earlier.
              m_tNextSmallTimer  =  time  (  NULL ) +  m_uZoneIndex  . Get32BitChunk  (3);

              // Start this zone.
              StartTimer  ();

              // If we are initializing the root node, read in our saved contact list.
              if  (( m_pSuperZone  ==  NULL ) && (  m_sFilename  . GetLength  () > 0))
                          ReadFile  ();


接着调用函数StartTime(),用来开始这个区域。在StartTime()函数内部添加事件CKademlia::AddEvent(this);


              time_t   tNow  time (  NULL  );
              // Start filling the tree, closest bins first.
              m_tNextBigTimer  =  tNow  SEC (10);
              CKademlia  :: AddEvent  (  this );

在调用完函数StartTime()函数后,从文件中读取以前保存的联系人。

在调用完函数Kademlia::CKademlia::Start();之后,Kademlia开始处理,转入函数 Kademlia:: CKademlia::Process()开始执行,在函数void CKademlia::Process()中调用函数 pZone->OnSmallTimer();即CRoutingZone中 OnSmallTimer().。


line 274:

if  ( pZone  ->  m_tNextSmallTimer  <=  tNow  )
                        {
                                      pZone  -> OnSmallTimer  ();
                                      pZone  -> m_tNextSmallTimer  MIN2S (1) +  tNow  ;
                        }


CRoutingZone中OnSmallTimer(),在此函数体内,当判断联系人为非空时,调用函数 CKademlia::GetUDPListener()->SendMyDetails_KADEMLIA2(KADEMLIA2_HELLO_REQ, pContact->GetIPAddress(), pContact->GetUDPPort());来发送本地节点的一些信息,其中函数的第一个参数是消息的类型, KADEMLIA2_HELLO_REQ表明是Kademlia 2.0网络的加入请求,相当于TCP/IP中的ACK,即表明这个消息是用来加入网络的。第二个参数是本地节点的IP,第三个节点是本地节点的端口。

              if  ( pContact  !=  NULL )
            {
                          pContact  -> CheckingType  ();
                          if  ( pContact  ->  GetVersion () >= 6){  /*48b*/
                                      if  ( thePrefs  .  GetDebugClientKadUDPLevel () > 0)
                                                  DebugSend  ( "KADEMLIA2_HELLO_REQ"  pContact  -> GetIPAddress  (),  pContact ->  GetUDPPort  ());
                                      CUInt128   uClientID  pContact ->  GetClientID  ();
                                      CKademlia  :: GetUDPListener  ()->  SendMyDetails (  KADEMLIA2_HELLO_REQ  ,  pContact  -> GetIPAddress  (),  pContact ->  GetUDPPort  (),  pContact  ->  GetVersion (),  pContact  -> GetUDPKey  (), &  uClientID false  );
                                      if  ( pContact  ->  GetVersion () >=  KADEMLIA_VERSION8_49b  ){
                                                  // FIXME:
                                                  // This is a bit of a work arround for statistic values. Normally we only count values from incoming HELLO_REQs for
                                                  // the firewalled statistics in order to get numbers from nodes which have us on their routing table,
                                                  // however if we send a HELLO due to the timer, the remote node won't send a HELLO_REQ itself anymore (but
                                                  // a HELLO_RES which we don't count), so count those statistics here. This isn't really accurate, but it should
                                                  // do fair enough. Maybe improve it later for example by putting a flag into the contact and make the answer count
                                                  CKademlia  :: GetPrefs  ()->  StatsIncUDPFirewalledNodes (  false  );
                                                  CKademlia  :: GetPrefs  ()->  StatsIncTCPFirewalledNodes (  false  );
                                    }

接着转入KademliaUDPListener.cpp中函数void CKademliaUDPListener::SendMyDetails_KADEMLIA2(byte byOpcode, uint32 uIP, uint16 uUDPPort)运行,主要是调用函数SendPacket(byPacket, uLen, uIP, uUDPPort);,SendPacket(byPacket, uLen, uIP, uUDPPort);函数在KademliaUDPListener.cpp内部,此函数体内部调用函数theApp.clientudp-> SendPacket(pPacket, ntohl(uDestinationHost), uDestinationPort);来发送包。


uint32   uLen  sizeof (  byPacket  ) -  byteIOResponse  .  GetAvailable ();
                          if  ( byKadVersion  >=  KADEMLIA_VERSION6_49aBETA ){
                                      if  ( isnulmd4  (  uCryptTargetID ->  GetDataPtr  ())){
                                                  DebugLogWarning  ( _T  (  "Sending hello response to crypt enabled Kad Node which provided an empty NodeID: %s (%u)" ),  ipstr  ( ntohl  (  uIP )),  byKadVersion  ); 
                                                  SendPacket  ( byPacket  uLen ,   uIP  ,  uUDPPort  targetUDPKey NULL  );
                                    }
                                      else
                                                  SendPacket  ( byPacket  uLen ,   uIP  ,  uUDPPort  targetUDPKey uCryptTargetID  );
                        }
                          else  {
                                      SendPacket  ( byPacket  uLen ,   uIP  ,  uUDPPort  , 0,  NULL );
                                      ASSERT  (  targetUDPKey  .  IsEmpty () );
                        }

KademliaUDPListener.cpp内部 CKademliaUDPListener  :: SendPacket之一:
{
              if  ( uLenData  < 2) {
                          ASSERT  (0);
                          return  ;
            }
              AddTrackedOutPacket  ( uDestinationHost  pbyData [1]);
              Packet  *  pPacket  new   Packet  ( OP_KADEMLIAHEADER  );
              pPacket  -> opcode  pbyData [1];
              pPacket  -> pBuffer  new   char  [ uLenData  +8];
              memcpy  ( pPacket  ->  pBuffer pbyData  +2,  uLenData  -2);
              pPacket  -> size  uLenData -2;
              if  (  uLenData  > 200 )
                          pPacket  -> PackPacket  ();
              theStats  . AddUpDataOverheadKad  (  pPacket ->  size  );
              theApp .clientudp -> SendPacket( pPacket , ntohl ( uDestinationHost), uDestinationPort , true
                        , ( uCryptTargetID != NULL ) ? uCryptTargetID-> GetData () : NULL
                        , true , targetUDPKey . GetKeyValue( theApp .GetPublicIP ( false)));
}

ClientUDPSocket.cpp中(565line)函数theApp.clientudp->SendPacket(pPacket, ntohl(uDestinationHost), uDestinationPort);体内部将刚才的消息包(或者叫数据包)加入到controlpacket_queue的队尾,

 controlpacket_queue.AddTail(newpending); // line586

 controlpacket_queue是一个链表,类型是CTypedPtrList<CPtrList, UDPPack*> controlpacket_queue;,

CTypedPtrList  < CPtrList  UDPPack *>  controlpacket_queue  ;

// ZZ:UploadBandWithThrottler (UDP) -->
     sendLocker .  Lock  ();
              controlpacket_queue  . AddTail  (  newpending );
     sendLocker .  Unlock  ();

     theApp .  uploadBandwidthThrottler  -> QueueForSendingControlPacket  (  this );
              return   true  ;
// <-- ZZ:UploadBandWithThrottler (UDP)


是通过 模板来实现的。接着继续调用函数theApp.uploadBandwidthThrottler- >QueueForSendingControlPacket(this);此时数据包在链表UploadBandwidthThrottler* uploadBandwidthThrottler;中排队。

类UploadBandwidthThrottler继承自CWinThread类,主要是作为线程来运行的。

类在初始化,在构造函数中调用函数 UINT AFX_CDECL UploadBandwidthThrottler::RunProc(LPVOID pParam),

UploadBandwidthThrottler  :: UploadBandwidthThrottler  (  void ) {
              m_SentBytesSinceLastCall  = 0;
              m_SentBytesSinceLastCallOverhead  = 0;
     m_highestNumberOfFullyActivatedSlots  = 0;

              threadEndedEvent   new  CEvent (0, 1);
              pauseEvent   new  CEvent (  TRUE   TRUE  );

              doRun   true  ;
              AfxBeginThread  ( RunProc  , (  LPVOID )  this  );
}

UINT   AFX_CDECL  UploadBandwidthThrottler ::  RunProc  ( LPVOID  pParam ) {
              DbgSetThreadName  ( "UploadBandwidthThrottler"  );
              InitThreadLocale  ();
              UploadBandwidthThrottler   uploadBandwidthThrottler  = (  UploadBandwidthThrottler *)  pParam  ;

              return   uploadBandwidthThrottler  ->  RunInternal ();
}


这个函数调用uploadBandwidthThrottler->RunInternal();,RunInternal()函 数主要用来发送来自socket的数据包,函数体内调用两个函数:

SocketSentBytes socketSentBytes = socket->SendControlData(allowedDataRate > 0?(UINT)(bytesToSpend - spentBytes):1, minFragSize);

以及


    if (  socket  !=  NULL  ) {
                     SocketSentBytes   socketSentBytes  socket ->  SendControlData  ( allowedDataRate  > 0?( UINT  )( bytesToSpend  spentBytes ):1,  minFragSize  );
                                                     uint32   lastSpentBytes  socketSentBytes  . sentBytesControlPackets  socketSentBytes .  sentBytesStandardPackets  ;
                                                     spentBytes  +=  lastSpentBytes  ;
                                                     spentOverhead  +=  socketSentBytes  .  sentBytesControlPackets ;
                                        }

   if (  neededBytes  > 0) {
                                                                             SocketSentBytes   socketSentBytes  socket  -> SendFileAndControlData  (  neededBytes minFragSize  );
                                                                             uint32   lastSpentBytes  socketSentBytes  . sentBytesControlPackets  socketSentBytes .  sentBytesStandardPackets  ;
                                                                             spentBytes  +=  lastSpentBytes  ;
                                                                             spentOverhead  +=  socketSentBytes  . sentBytesControlPackets  ;

                             if  ( lastSpentBytes  > 0 &&  slotCounter  <  m_highestNumberOfFullyActivatedSlots  ) {
                                 m_highestNumberOfFullyActivatedSlots   slotCounter  ;
                            }
                                                                }

SocketSentBytes socketSentBytes = socket->SendFileAndControlData(neededBytes, minFragSize);

其中的socket类型是ThrottledFileSocket*,在类ThrottledFileSocket中这两个函数被定义为虚函数,

class   ThrottledFileSocket  public   ThrottledControlSocket
{
public  :
     virtual   SocketSentBytes   SendFileAndControlData  (  uint32   maxNumberOfBytesToSend   uint32  minFragSize  ) = 0;
     virtual   DWORD   GetLastCalledSend  () = 0;
     virtual   uint32     GetNeededBytes  () = 0;
              virtual   bool             IsBusy  ()  const  = 0;
     virtual   bool       HasQueues  ()  const  = 0;
              virtual   bool             UseBigSendBuffer  ()                                                                                            {  return   false  ; }
};


而 且在这个类内部没有具体实现,它们的实现在类CClientUDPSocket中,类CClientUDPSocket继承自 CAsyncSocket以 及 ThrottledControlSocket,如下代码:

class CClientUDPSocket : public CAsyncSocket, public ThrottledControlSocket // ZZ:UploadBandWithThrottler (UDP)。

socket->SendControlData(allowedDataRate > 0?(UINT)(bytesToSpend - spentBytes):1, minFragSize);


class   CClientUDPSocket  public   CAsyncSocket   public  CEncryptedDatagramSocket public   ThrottledControlSocket   // ZZ:UploadBandWithThrottler (UDP)
{
public  :
              CClientUDPSocket  ();
              virtual  ~ CClientUDPSocket  ();

              bool         Create  ();
              bool         Rebind  ();
              uint16      GetConnectedPort  ()                               {  return   m_port  ; }
              bool         SendPacket  (  Packet packet   uint32  dwIP uint16   nPort  bool   bEncrypt   const  uchar   pachTargetClientHash  );
     SocketSentBytes    SendControlData  ( uint32  maxNumberOfBytesToSend uint32   minFragSize  );  // ZZ:UploadBandWithThrottler (UDP)
protected  :


以及

SocketSentBytes socketSentBytes = socket->SendFileAndControlData(neededBytes, minFragSize);的实现体在ClientUDPSocket.cpp中424行:[ps:newversion中可能没这个了]

SocketSentBytes CClientUDPSocket::SendControlData(uint32 maxNumberOfBytesToSend, uint32 /*minFragSize*/){ // ZZ:UploadBandWithThrottler (UDP)

在它们内部调用了函数SendTo,if (!SendTo(sendbuffer, cur_packet->packet->size+2, cur_packet->dwIP, cur_packet->nPort))(在ClientUDPSocket.cpp中528行)。这个函数是类CClientUDPSocket 的成员函数。int CClientUDPSocket::SendTo(char* lpBuf,int nBufLen,uint32 dwIP, uint16 nPort),在这个函数体内调用类CAsyncSocket的成员函数uint32 result = CAsyncSocket::SendTo(lpBuf,nBufLen,nPort,ipstr(dwIP));,类CAsyncSocket是MFC 的类库中的一个类。【NND,终于找到头了】


   if  (!  SendTo  (( char  *)  sendbuffer nLen   cur_packet  ->  dwIP cur_packet  -> nPort  )){
                 sentBytes  +=  nLen  // ZZ:UploadBandWithThrottler (UDP)

                                                  controlpacket_queue  . RemoveHead  ();
                                                  delete   cur_packet  ->  packet ;
                                                  delete   cur_packet  ;
            }

int   CClientUDPSocket  ::  SendTo (  char   lpBuf  ,  int   nBufLen  , uint32  dwIP uint16   nPort  ){
              // NOTE: *** This function is invoked from a *different* thread!
              uint32 result CAsyncSocket:: SendTo (lpBuf , nBufLen, nPort ,ipstr ( dwIP));
              if  ( result  == (  uint32 )  SOCKET_ERROR  ){
                          uint32   error  GetLastError ();
                          if  ( error  ==  WSAEWOULDBLOCK ){
                                      m_bWouldBlock   true  ;
                                      return  -1;
                        }
                          if  ( thePrefs  .  GetVerbose ())
                                      DebugLogError  ( _T  (  "Error: Client UDP socket, failed to send data to %s:%u: %s" ),  ipstr (  dwIP  ),  nPort  GetErrorMessage (  error  , 1));
            }
              return  0;
}

至此,本地节点加入网络的请求就发送完毕。


  • 下面讲述本地节点在接收到来自其他节点的回应后在本地采取的一些措施从而把自己加入到网络内。

当网络事件发生时(即本地网卡接收到数据包),“socket窗口”接收WM_SOCKET_NOTIFY消息,消息处理函数OnSocketNotify被调用,。“socket窗口”的定义和消息处理是MFC实现的,其中OnSocketNotify函数定义如下:

LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)

{

CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);

CSocket::ProcessAuxQueue();

return 0L;

}

在CSocket::ProcessAuxQueue();函数中回调CAsyncSocket的成员函数DoCallBack,DoCallBack调用事件处理函数OnReceive。

int PASCAL CSocket::ProcessAuxQueue()

{

……………………//省略部分

if (pMsg->message == WM_SOCKET_NOTIFY)

{

CAsyncSocket::DoCallBack(pMsg->wParam, pMsg->lParam);

}

………………//省略部分

return nCount;

}

void PASCAL CAsyncSocket::DoCallBack(WPARAM wParam, LPARAM lParam)

{

……………………//省略部分

pSocket->OnReceive(nErrorCode);

/*pSocket类型是:CClientUDPSocket,因为类CClientUDPSocket继承了类 CAsyncSocket,而OnReceive在CAsyncSocket定义的虚函数,OnReceive在CClientUDPSocket中重新 做了实现,因此调用的时候会转到CClientUDPSocket中OnReceive执行。*/

}

void CClientUDPSocket::OnReceive(int nErrorCode)

{

……………………

case OP_KADEMLIAHEADER:

{

// theStats.AddDownDataOverheadKad(length);

if (length >= 2)

Kademlia::CKademlia::ProcessPacket(buffer, length, ntohl(sockAddr.sin_addr.S_un.S_addr), ntohs(sockAddr.sin_port));

else

throw CString(_T("Kad packet too short"));

break;

}

……………………

}

接着调用在kademlia.cpp中定义的函数ProcessPacket。

void CKademlia::ProcessPacket(const byte *pbyData, uint32 uLenData, uint32 uIP, uint16 uPort)

{

if( m_pInstance && m_pInstance->m_pUDPListener )

m_pInstance->m_pUDPListener->ProcessPacket( pbyData, uLenData, uIP, uPort);

}

转入KademliaUDPListener类中ProcessPacket函数运行。

void CKademliaUDPListener::ProcessPacket(const byte* pbyData, uint32 uLenData, uint32 uIP, uint16 uUDPPort)

{

//………………………………省略部分

switch (byOpcode)

{

………………………………//省略部分

case KADEMLIA_RES:

if (thePrefs.GetDebugClientKadUDPLevel() > 0)

DebugRecv("KADEMLIA_RES", uIP, uUDPPort);

Process_KADEMLIA_RES(pbyPacketData, uLenPacket, uIP, uUDPPort);

break;

………………………………//省略部分

}

}

转入函数Process_KADEMLIA_RES(pbyPacketData, uLenPacket, uIP, uUDPPort);执行:

void CKademliaUDPListener::Process_KADEMLIA_RES (const byte *pbyPacketData, uint32 uLenPacket, uint32 uIP, uint16 uUDPPort) 【我拦截它就ok了】

{

//……………………

if(CKademlia::GetPrefs()->GetRecheckIP())

{

FirewalledCheck(uIP, uUDPPort);

if (thePrefs.GetDebugClientKadUDPLevel() > 0)

DebugSend("KADEMLIA_HELLO_REQ", uIP, uUDPPort);

SendMyDetails(KADEMLIA_HELLO_REQ, uIP, uUDPPort);

}

if(::IsGoodIPPort(ntohl(uIPResult),uUDPPortResult))

{

pRoutingZone->Add(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, 0);

pResults->push_back(new CContact(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, uTarget, 0));

}

}

}

CSearchManager::ProcessResponse(uTarget, uIP, uUDPPort, pResults);

}

在这个函数体内部主要包括对4个函数的调用,分别是:

SendMyDetails(KADEMLIA_HELLO_REQ, uIP, uUDPPort);

pRoutingZone->Add(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, 0);

pResults->push_back(new CContact(uIDResult, uIPResult, uUDPPortResult, uTCPPortResult, uTarget, 0));

CSearchManager::ProcessResponse(uTarget, uIP, uUDPPort, pResults);

其中第一个函数是在判断自己在防火墙或者NAT之后重新发送本地节点信息的函数,包括重新得到的IP地址以及端口。

第二和第三个函数用来添加此节点作为联系人之一。

第三个函数是将此消息转入到CSearchManager中相应处理响应的函数进行处理。

void CSearchManager::ProcessResponse(const CUInt128 &uTarget, uint32 uFromIP, uint16 uFromPort, ContactList *plistResults)

{

pSearch->ProcessResponse(uFromIP, uFromPort, plistResults);// pSearch是 CSearch类的指针

}

进一步转入到pSearch->ProcessResponse(uFromIP, uFromPort, plistResults)中执行。

void CSearch::ProcessResponse(uint32 uFromIP, uint16 uFromPort, ContactList *plistResults)

{

// Not interested in responses for FIND_NODE.

// Once we get a results we stop the search.

// These contacts are added to contacts by UDPListener.

if (m_uType == NODE)

{

// Note we got an answer

m_uAnswers++;

// We clear the possible list to force the search to stop.

// We do this so the user has time to visually see the results.

m_mapPossible.clear();

delete plistResults;

// Update search on the GUI.

//IMPREVIEW theApp.emuledlg->kademliawnd->searchList->SearchRef(this);

return;

}

}

在这个函数内部我们将响应的节点数目增加一。

后面陆续接收到的消息处理流程与上述情形相似,只是对于不同的消息采取的响应以及动作并不相同。
发布了202 篇原创文章 · 获赞 48 · 访问量 56万+

猜你喜欢

转载自blog.csdn.net/jo_say/article/details/8082491