读代码读到现在,补充一点关于Kademlia网络的理论知识。
Kademlia 是一种结构化的覆盖网络(Structured Overlay Network)。所谓覆盖网络,就是一种在物理的Internet之上再次构建的虚拟网络。所有参与的节点都知道一部分其它节点的IP地址,这些节点称为它的邻居。如果需要查找什么资源,它先向自己的邻居询问,邻居则在本地寻找,如果找不到,就把这个查询转发到它的邻居处,希望能够查找到相应的结果。
覆盖网络分为结构化和非结构化的两种,它们的区别在于一个节点所知道的其它节点是否有特定的规律。
在非结构化的覆盖网中,节点之间邻居关系的建立没有特定的规则,节点随机地与其它节点建立邻居关系。在非结构化网络中,要进行查询,会采取一种叫做泛洪(flooding)的方法,一个节点如果在本地没有查找到想要的结果,会把查找请求转发到它的邻居中,然后再通过邻居的邻居这种方式来进行一步步的查找。但是这种方法如果处理不好,会造成整个网络的消息负载过大。已经有不少文章对于优化非结构化覆盖网络中的查询进行了很深入的探讨。
对于结构化的覆盖网络,它的特点是,每个节点会选择性地与其它节点建立邻居关系。搜索资源的过程中,节点转发搜索请求时,能够依照一定的规律选择合适的节点来进行。这样能大大减少搜索的代价,提升搜索的效率。
结构化的覆盖网络通常要求每一个节点随机生成一个ID,用以判断各个节点之间的关系。这个ID和它所在的物理网络必须是没有关系的。对于Kademlia网络来说,这个ID是一个128位的数值,所有的节点都用这个ID来衡量自己与其它节点的逻辑距离。而逻辑距离的计算方法就是将两个节点进行异或(XOR)操作。在Kademlia网络的形成过程中,每个节点选择邻居的原则是:离自己逻辑距离越近的节点越有可能被加入到自己的邻居节点列表中,具体来说就是在每次新得到一个节点的信息的时候,是否把它加入到自己的邻居节点列表是根据逻辑距离的远近来处理的。后面分析具体程序的代码时会有说明。
结构化网络的好处就是如果我们要寻找一个距离某ID逻辑距离足够近的节点,我们可以保证在O(logn)级别的 跳数找到。只要先寻找自己已知的离目标ID逻辑距离足够近的节点,然后再问它知不知道更近的,然后就这样持续下去。
当需要发布资源的时候,就对文件或关键字进行hash,这样能够计算出一个128位的ID,然后寻找到离这个结果逻辑距离最近的节点,把文件或者关键 字的信息发送给它,让它存起来。当有人要搜索同样的东西时,使用同一个hash算法,计算出同样的ID,然后去搜索那些和这个ID逻辑距离相近的节点。因为它知道,如果网络中真有这些资源的话,这些节点是最有可能知道这些信息的。
由此我们可以看出,结构化网络的资源查找效率是很高的,但和非结构化覆盖网络相比,缺点是不能进行复杂查询,即只能通过简单的关键字或者文件的hash值进行查找。非结构化网络的查找本身就是随机转发的,每个收到查询请求的节点都对本地所具有的资源掌握的很清楚,因此自然可以支持复杂查询,但是显然非结构化的网络支持的复杂查询不太可能动员所有的节点都来做这一动作。
上面这几段内容转自:eMule源代码学习心得,致敬。
从UI操作开始。
上面的输入框中输入要查找的关键字。输入框右边的“类型”下拉框,可以选择搜索的网络类型,我们可以选择“Kad”,来从Kad网络搜索资源。我们在输入完了查找关键字之后,键入回车或点击按钮“开始”可以启动搜索。
启动Kad网络的那个界面,如我们前面所了解,其构造是在amule-2.3.1/src/ServerWnd.cpp文件的CServerWnd类中进行的。根据aMule主UI的结构,有理由猜测,创建CServerWnd对象的地方也同时为“搜索”、“下载”、“共享文件”等界面创建了UI组件。CServerWnd对象创建的地方在amule-2.3.1/src/amuleDlg.cpp文件,CamuleDlg::CamuleDlg()函数中,在这个函数中还能看到其它一些有意思的东西呢:
m_serverwnd = new CServerWnd(p_cnt, m_srv_split_pos); AddLogLineN(wxEmptyString); AddLogLineN(wxT(" - ") + CFormat(_("This is aMule %s based on eMule.")) % GetMuleVersion()); AddLogLineN(wxT(" ") + CFormat(_("Running on %s")) % wxGetOsDescription()); AddLogLineN(wxT(" - ") + wxString(_("Visit http://www.amule.org to check if a new version is available."))); AddLogLineN(wxEmptyString); #ifdef ENABLE_IP2COUNTRY m_GeoIPavailable = true; m_IP2Country = new CIP2Country(theApp->ConfigDir); #else m_GeoIPavailable = false; #endif m_searchwnd = new CSearchDlg(p_cnt); m_transferwnd = new CTransferWnd(p_cnt); m_sharedfileswnd = new CSharedFilesWnd(p_cnt); m_statisticswnd = new CStatisticsDlg(p_cnt, theApp->m_statistics); m_chatwnd = new CChatWnd(p_cnt); m_kademliawnd = CastChild(wxT("kadWnd"), CKadDlg); m_serverwnd->Show(false); m_searchwnd->Show(false); m_transferwnd->Show(false); m_sharedfileswnd->Show(false); m_statisticswnd->Show(false); m_chatwnd->Show(false);
瞅瞅那几个变量的名字,不难猜到,“搜索”界面主要是在CSearchDlg类中创建的,而CSearchDlg类在amule-2.3.1/src/SearchDlg.cpp文件中定义。界面中最上面那个输入查找关键字的文本框,应该是一个wxTextCtrl。在amule-2.3.1/src/SearchDlg.cpp文件搜索中搜索wxTextCtrl,可以发现,那个文本框的ID应当为IDC_SEARCHNAME。
搜索过程可以通过两个事件触发,分别是文本输入框的回车事件,和“开始”按钮的点击事件:
EVT_BUTTON( IDC_STARTS, CSearchDlg::OnBnClickedStart) EVT_TEXT_ENTER( IDC_SEARCHNAME, CSearchDlg::OnBnClickedStart)事件被触发之后,会执行CSearchDlg::OnBnClickedStart()函数。随后的执行流程为CSearchDlg::OnBnClickedStart() -> CSearchDlg::StartNewSearch() -> CSearchList::StartNewSearch()(amule-2.3.1/src/SearchList.cpp文件) -> Kademlia::CSearchManager::PrepareFindKeywords()。Kademlia::CSearchManager::PrepareFindKeywords()的执行过程如下所示(在文件amule-2.3.1/src/kademlia/kademlia/SearchManager.cpp中):
CSearch* CSearchManager::PrepareFindKeywords(const wxString& keyword, uint32_t searchTermsDataSize, const uint8_t *searchTermsData, uint32_t searchid) { AddLogLineNS(wxT("") + wxString(wxT("CSearchManager::PrepareFindKeywords, keyword is ")) + keyword); // Create a keyword search object. CSearch *s = new CSearch; try { // Set search to a keyword type. s->SetSearchTypes(CSearch::KEYWORD); // Make sure we have a keyword list GetWords(keyword, &s->m_words, true); if (s->m_words.size() == 0) { throw wxString(_("Kademlia: search keyword too short")); } wxString wstrKeyword = s->m_words.front(); AddLogLineNS(CFormat(_("Keyword for search: %s")) % wstrKeyword); // Kry - I just decided to assume everyone is unicoded // GonoszTopi - seconded KadGetKeywordHash(wstrKeyword, &s->m_target); // Verify that we are not already searching for this target. if (AlreadySearchingFor(s->m_target)) { throw _("Kademlia: Search keyword is already on search list: ") + wstrKeyword; } s->SetSearchTermData(searchTermsDataSize, searchTermsData); // Inc our searchID // If called from external client use predefined search id s->SetSearchID((searchid & 0xffffff00) == 0xffffff00 ? searchid : ++m_nextID); // Insert search into map m_searches[s->GetTarget()] = s; // Start search s->Go(); } catch (const CEOFException& err) { delete s; wxString strError = wxT("CEOFException in ") + wxString::FromAscii(__FUNCTION__) + wxT(": ") + err.what(); throw strError; } catch (const CInvalidPacket& err) { delete s; wxString strError = wxT("CInvalidPacket exception in ") + wxString::FromAscii(__FUNCTION__) + wxT(": ") + err.what(); throw strError; } catch (...) { delete s; throw; } return s; }
这个函数中,主要做了这样的一些事情:
1. 创建了一个CSearch对象s,设置其SearchType为CSearch::KEYWORD。
2. 解析传进来的关键字,将关键字适当地拆分为几个单词。(GetWords(keyword, &s->m_words, true))
3. 取出第一个单词作为后面搜索的关键字。
4. 计算关键字的Hash值。(KadGetKeywordHash(wstrKeyword, &s->m_target))
5. 通过计算出的Hash值,检查这个搜索是否已经在进行了,如果已经在进行了,则返回。否则继续执行。
6. 设置CSearch s的SearchTermData和SearchID。
7. 将CSearch s放进searchs列表。
8. 执行CSearch s的GO()启动搜索。(s->Go())
这里可以简单看一下,搜索关键字的Hash值是如何计算的:
// Global function. #include "../../CryptoPP_Inc.h" void KadGetKeywordHash(const wxString& rstrKeyword, Kademlia::CUInt128* pKadID) { byte Output[16]; #ifdef __WEAK_CRYPTO__ CryptoPP::Weak::MD4 md4_hasher; #else CryptoPP::MD4 md4_hasher; #endif // This should be safe - we assume rstrKeyword is ANSI anyway. char* ansi_buffer = strdup(unicode2UTF8(rstrKeyword)); //printf("Kad keyword hash: UTF8 %s\n",ansi_buffer); md4_hasher.CalculateDigest(Output,(const unsigned char*)ansi_buffer,strlen(ansi_buffer)); //DumpMem(Output,16); free(ansi_buffer); pKadID->SetValueBE(Output); }
搜索过程最重要的还是CSearch::Go()函数,在文件amule-2.3.1/src/kademlia/kademlia/Search.cpp中:
void CSearch::Go() { // Start with a lot of possible contacts, this is a fallback in case search stalls due to dead contacts if (m_possible.empty()) { CUInt128 distance(CKademlia::GetPrefs()->GetKadID() ^ m_target); CKademlia::GetRoutingZone()->GetClosestTo(3, m_target, distance, 50, &m_possible, true, true); } if (!m_possible.empty()) { //Lets keep our contact list entries in mind to dec the inUse flag. for (ContactMap::iterator it = m_possible.begin(); it != m_possible.end(); ++it) { m_inUse[it->first] = it->second; } wxASSERT(m_possible.size() == m_inUse.size()); // Take top ALPHA_QUERY to start search with. int count = m_type == NODE ? 1 : min(ALPHA_QUERY, (int)m_possible.size()); // Send initial packets to start the search. ContactMap::iterator it = m_possible.begin(); for (int i = 0; i < count; i++) { CContact *c = it->second; // Move to tried m_tried[it->first] = c; // Send the KadID so other side can check if I think it has the right KadID. // Send request SendFindValue(c); ++it; } } }
这个函数比较清晰。总体而言,它首先计算出本节点与目标资源之间的距离,然后找到与目标资源逻辑距离更进的节点并存放在m_possible map中,最后向这些节点发送查找请求。
在这里,会根据查找的类型来确定向其发送查找请求的节点的个数。如果要查找的是一个节点,则只向一个最近节点发送请求。如果是其他的查找类型,则向最近的多个节点发送查找请求,但请求的个数不多于ALPHA_QUERY个,这一版本也就是3个,同时不多于查找到的所有联系人的个数。发送请求的过程,则通过 CSearch::SendFindValue()函数完成:
void CSearch::SendFindValue(CContact *contact, bool reaskMore) { // Found a node that we think has contacts closer to our target. try { if (m_stopping) { return; } CMemFile packetdata(33); // The number of returned contacts is based on the type of search. uint8_t contactCount = GetRequestContactCount(); if (reaskMore) { if (m_requestedMoreNodesContact == NULL) { m_requestedMoreNodesContact = contact; wxASSERT(contactCount == KADEMLIA_FIND_VALUE); contactCount = KADEMLIA_FIND_VALUE_MORE; } else { wxFAIL; } } if (contactCount > 0) { packetdata.WriteUInt8(contactCount); } else { return; } // Put the target we want into the packet. packetdata.WriteUInt128(m_target); // Add the ID of the contact we're contacting for sanity checks on the other end. packetdata.WriteUInt128(contact->GetClientID()); if (contact->GetVersion() >= 2) { if (contact->GetVersion() >= 6) { CUInt128 clientID = contact->GetClientID(); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_REQ, contact->GetIPAddress(), contact->GetUDPPort(), contact->GetUDPKey(), &clientID); } else { CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_REQ, contact->GetIPAddress(), contact->GetUDPPort(), 0, NULL); wxASSERT(contact->GetUDPKey() == CKadUDPKey(0)); } 。。。。。。 } else { wxFAIL; } } catch (const CEOFException& err) { AddDebugLogLineC(logKadSearch, wxT("CEOFException in CSearch::SendFindValue: ") + err.what()); } catch (const CInvalidPacket& err) { AddDebugLogLineC(logKadSearch, wxT("CInvalidPacket Exception in CSearch::SendFindValue: ") + err.what()); } catch (const wxString& e) { AddDebugLogLineC(logKadSearch, wxT("Exception in CSearch::SendFindValue: ") + e); } }
Kademlia网络数据包的整体结构与其它许多网络协议数据包的整体结构没有太大的区别,数据包的开头是一个Packet Header,后面是Packet数据。在这个函数中,构造的主要是数据包的Packet数据部分。我们可以看一下,关键字查询请求数据包数据部分的结构:
请求返回的联系人的个数,根据请求的类型不同而不同(8位1字节) -> 要查找的目标节点的节点ID(128位16字节)-> 用于接收端进行消息校验的目标联系人节点的节点ID(128位16字节)。
数据包数据部分总共33个字节。
Packet数据构造好了之后,会调用CKademlia::GetUDPListener()->SendPacket()发送数据,在文件amule-2.3.1/src/kademlia/net/KademliaUDPListener.cpp中:
void CKademliaUDPListener::SendPacket(const CMemFile &data, uint8_t opcode, uint32_t destinationHost, uint16_t destinationPort, const CKadUDPKey& targetKey, const CUInt128* cryptTargetID) { AddTrackedOutPacket(destinationHost, opcode); CPacket* packet = new CPacket(data, OP_KADEMLIAHEADER, opcode); if (packet->GetPacketSize() > 200) { packet->PackPacket(); } uint8_t cryptData[16]; uint8_t *cryptKey; if (cryptTargetID != NULL) { cryptKey = (uint8_t *)&cryptData; cryptTargetID->StoreCryptValue(cryptKey); } else { cryptKey = NULL; } theStats::AddUpOverheadKad(packet->GetPacketSize()); theApp->clientudp->SendPacket(packet, wxUINT32_SWAP_ALWAYS(destinationHost), destinationPort, true, cryptKey, true, targetKey.GetKeyValue(theApp->GetPublicIP(false))); }
在这里,Packet数据会被再次打包,构造一个CPacket对象。可以看一下上面的代码中所用到的CPacket构造函数(amule-2.3.1/src/Packet.cpp中):
CPacket::CPacket(const CMemFile& datafile, uint8 protocol, uint8 ucOpcode) { size = datafile.GetLength(); opcode = ucOpcode; prot = protocol; m_bSplitted = false; m_bLastSplitted = false; m_bPacked = false; m_bFromPF = false; memset(head, 0, sizeof head); tempbuffer = NULL; completebuffer = new byte[size + sizeof(Header_Struct)/*Why this 4?*/]; pBuffer = completebuffer + sizeof(Header_Struct); // Write contents of MemFile to buffer (while keeping original position in file) off_t position = datafile.GetPosition(); datafile.Seek(0, wxFromStart); datafile.Read(pBuffer, size); datafile.Seek(position, wxFromStart); } byte* CPacket::DetachPacket() { if (completebuffer) { if (!m_bSplitted) { memcpy(completebuffer, GetHeader(), sizeof(Header_Struct)); } byte* result = completebuffer; completebuffer = pBuffer = NULL; return result; } else{ if (tempbuffer){ delete[] tempbuffer; tempbuffer = NULL; } tempbuffer = new byte[size+sizeof(Header_Struct)+4 /* Why this 4?*/]; memcpy(tempbuffer,GetHeader(),sizeof(Header_Struct)); memcpy(tempbuffer+sizeof(Header_Struct),pBuffer,size); byte* result = tempbuffer; tempbuffer = 0; return result; } } byte* CPacket::GetHeader() { wxASSERT( !m_bSplitted ); Header_Struct* header = (Header_Struct*) head; header->command = opcode; header->eDonkeyID = prot; header->packetlength = ENDIAN_SWAP_32(size + 1); return head; }
amule-2.3.1/src/OtherStructs.h中:
struct Header_Struct{ int8 eDonkeyID; int32 packetlength; int8 command; }
CPacket主要是在Packet数据的前面又加了一个Packet Header,来描述协议等信息。在一个CPacket真的要被发送时,它的DetachPacket()函数会被调用,以最终将CPacket构造为一个字节流。
如果Packet数据量过大,也就是CPacket整体大小超过200字节,则在CKademlia::GetUDPListener()::SendPacket()中,Packet数据将会被压缩。CPacket是UDP socket真正要发送到网络上的数据包,通过CMuleUDPSocket::SendPacket()函数。
对于我们的关键字查询请求而言,它的opcode为KADEMLIA2_REQ,它的protocol则为OP_KADEMLIAHEADER。
看了关键字搜索的发起过程之后,我们心中难免就有一个疑问,这些消息是如何被接收的,接收到这个请求的节点又会如何处理这个消息?
这还要从aMule的启动说起。在CamuleApp::ReinitializeNetwork()函数中,会创建一个CClientUDPSocket对象,监听一个UDP端口,默认是4672,以此来接收从其它节点发送的与Kademlia网络相关的消息。具体代码如下(amule-2.3.1/src/amule.cpp):
// Create the UDP socket. // Used for extended eMule protocol, Queue Rating, File Reask Ping. // Also used for Kademlia. // Default is port 4672. myaddr[3] = myaddr[1]; myaddr[3].Service(thePrefs::GetUDPPort()); clientudp = new CClientUDPSocket(myaddr[3], thePrefs::GetProxyData()); if (!thePrefs::IsUDPDisabled()) {
来看CClientUDPSocket构造函数的具体实现(amule-2.3.1/src/ClientUDPSocket.cpp):
CClientUDPSocket::CClientUDPSocket(const amuleIPV4Address& address, const CProxyData* ProxyData) : CMuleUDPSocket(wxT("Client UDP-Socket"), ID_CLIENTUDPSOCKET_EVENT, address, ProxyData) { if (!thePrefs::IsUDPDisabled()) { Open(); } }
CClientUDPSocket继承自CMuleUDPSocket,这里有调用到CMuleUDPSocket的构造函数来构造CMuleUDPSocket的部分,并在UDP没有被禁用的情况下,调用Open()函数,Open()函数是接口和实现同时继承CMuleUDPSocket。这里我们看一下这几个函数的定义(amule-2.3.1/src/MuleUDPSocket.cpp):
CMuleUDPSocket::CMuleUDPSocket(const wxString& name, int id, const amuleIPV4Address& address, const CProxyData* ProxyData) : m_busy(false), m_name(name), m_id(id), m_addr(address), m_proxy(ProxyData), m_socket(NULL) { } void CMuleUDPSocket::Open() { wxMutexLocker lock(m_mutex); CreateSocket(); } void CMuleUDPSocket::CreateSocket() { wxCHECK_RET(!m_socket, wxT("Socket already opened.")); m_socket = new CEncryptedDatagramSocket(m_addr, wxSOCKET_NOWAIT, m_proxy); m_socket->SetClientData(this); m_socket->SetEventHandler(*theApp, m_id); m_socket->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_OUTPUT_FLAG | wxSOCKET_LOST_FLAG); m_socket->Notify(true); if (!m_socket->Ok()) { AddDebugLogLineC(logMuleUDP, wxT("Failed to create valid ") + m_name); DestroySocket(); } else { AddLogLineN(wxString(wxT("Created ")) << m_name << wxT(" at port ") << m_addr.Service()); } }
在Open()函数中会调用CreateSocket()创建一个CEncryptedDatagramSocket来执行底层的socket监听的任务。可以看到,在CreateSocket()函数中会给新创建的soket设置一个EventHandler。也就是说,在socket监听到有数据传进来时会产生一个事件,事件的ID如CClientUDPSocket的构造函数所指明的,为ID_CLIENTUDPSOCKET_EVENT,而处理事件的对象将为theApp对象。对于我们正在分析的GUI版的aMule而言,theApp的类型为CamuleGuiApp,我们可以看到这个class的Event注册表中有这样的几行(amule-2.3.1/src/amule-gui.cpp):
BEGIN_EVENT_TABLE(CamuleGuiApp, wxApp) // Socket handlers // Listen Socket EVT_SOCKET(ID_LISTENSOCKET_EVENT, CamuleGuiApp::ListenSocketHandler) // UDP Socket (servers) EVT_SOCKET(ID_SERVERUDPSOCKET_EVENT, CamuleGuiApp::UDPSocketHandler) // UDP Socket (clients) EVT_SOCKET(ID_CLIENTUDPSOCKET_EVENT, CamuleGuiApp::UDPSocketHandler)
也就意味着,当CClientUDPSocket/CEncryptedDatagramSocket监听到有消息进来而产生事件,该事件将会由CamuleGuiApp::UDPSocketHandler()来处理。这个函数的实现如下:
void CamuleApp::UDPSocketHandler(wxSocketEvent& event) { CMuleUDPSocket* socket = (CMuleUDPSocket*)(event.GetClientData()); wxCHECK_RET(socket, wxT("No socket owner specified.")); if (IsOnShutDown() || thePrefs::IsUDPDisabled()) return; if (!IsRunning()) { if (event.GetSocketEvent() == wxSOCKET_INPUT) { // Back to the queue! theApp->AddPendingEvent(event); return; } } switch (event.GetSocketEvent()) { case wxSOCKET_INPUT: socket->OnReceive(0); break; case wxSOCKET_OUTPUT: socket->OnSend(0); break; case wxSOCKET_LOST: socket->OnDisconnected(0); break; default: wxFAIL; break; } }
对于我们分析的case而言,会调用socket的OnReceive()函数,也就是CMuleUDPSocket::OnReceive()函数(amule-2.3.1/src/MuleUDPSocket.cpp):
void CMuleUDPSocket::OnReceive(int errorCode) { AddDebugLogLineN(logMuleUDP, CFormat(wxT("Got UDP callback for read: Error %i Socket state %i")) % errorCode % Ok()); char buffer[UDP_BUFFER_SIZE]; wxIPV4address addr; unsigned length = 0; bool error = false; int lastError = 0; { wxMutexLocker lock(m_mutex); if (errorCode || (m_socket == NULL) || !m_socket->Ok()) { DestroySocket(); CreateSocket(); return; } length = m_socket->RecvFrom(addr, buffer, UDP_BUFFER_SIZE).LastCount(); error = m_socket->Error(); lastError = m_socket->LastError(); } const uint32 ip = StringIPtoUint32(addr.IPAddress()); const uint16 port = addr.Service(); if (error) { OnReceiveError(lastError, ip, port); } else if (length < 2) { // 2 bytes (protocol and opcode) is the smallets possible packet. AddDebugLogLineN(logMuleUDP, m_name + wxT(": Invalid Packet received")); } else if (!ip) { // wxFAIL; AddLogLineNS(wxT("Unknown ip receiving a UDP packet! Ignoring: '") + addr.IPAddress() + wxT("'")); } else if (!port) { // wxFAIL; AddLogLineNS(wxT("Unknown port receiving a UDP packet! Ignoring")); } else if (theApp->clientlist->IsBannedClient(ip)) { AddDebugLogLineN(logMuleUDP, m_name + wxT(": Dropped packet from banned IP ") + addr.IPAddress()); } else { AddDebugLogLineN(logMuleUDP, (m_name + wxT(": Packet received (")) << addr.IPAddress() << wxT(":") << port << wxT("): ") << length << wxT("b")); OnPacketReceived(ip, port, (byte*)buffer, length); } }
这里会做一些检查,然后调用OnPacketReceived(ip, port, (byte*)buffer, length),这是一个虚函数,实际调用到的将会是CClientUDPSocket::OnPacketReceived()函数,在这个函数中可以看到如下的几行(amule-2.3.1/src/ClientUDPSocket.cpp):
if (packetLen >= 1) { try { switch (protocol) { case OP_EMULEPROT: AddLogLineNS(wxT("") + wxString(wxT("CClientUDPSocket::OnPacketReceived protocol OP_EMULEPROT "))); ProcessPacket(decryptedBuffer + 2, packetLen - 2, opcode, ip, port); break; case OP_KADEMLIAHEADER: AddLogLineNS(wxT("") + wxString(wxT("CClientUDPSocket::OnPacketReceived protocol OP_KADEMLIAHEADER "))); theStats::AddDownOverheadKad(length); if (packetLen >= 2) { Kademlia::CKademlia::ProcessPacket(decryptedBuffer, packetLen, wxUINT32_SWAP_ALWAYS(ip), port, (Kademlia::CPrefs::GetUDPVerifyKey(ip) == receiverVerifyKey), Kademlia::CKadUDPKey(senderVerifyKey, theApp->GetPublicIP(false))); } else { throw wxString(wxT("Kad packet too short")); } break;
可见,protocol为OP_KADEMLIAHEADER,消息将最终被转给Kademlia::CKademlia::ProcessPacket()函数处理,来看这个函数的实现(amule-2.3.1/src/kademlia/kademlia/Kademlia.cpp):
void CKademlia::ProcessPacket(const uint8_t *data, uint32_t lenData, uint32_t ip, uint16_t port, bool validReceiverKey, const CKadUDPKey& senderKey) { AddLogLineNS(wxT("") + wxString(wxT("CKademlia::ProcessPacket from ")) + CFormat(_("ip: 0x%x, port %u.")) % ip % port); try { if( instance && instance->m_udpListener ) { instance->m_udpListener->ProcessPacket(data, lenData, ip, port, validReceiverKey, senderKey); } } catch (const wxString& DEBUG_ONLY(error)) { AddDebugLogLineN(logKadMain, CFormat(wxT("Exception on Kad ProcessPacket while processing packet (length = %u) from %s:")) % lenData % KadIPPortToString(ip, port)); AddDebugLogLineN(logKadMain, error); throw; } catch (...) { AddDebugLogLineN(logKadMain, wxT("Unhandled exception on Kad ProcessPacket")); throw; } }
由此可见,处理消息的任务又被委托给了CKademliaUDPListener::ProcessPacket(),在这个函数里,同样也是一长串的swich-case,并将消息处理的任务继续委托。可以看到这样的一个case(amule-2.3.1/src/kademlia/net/KademliaUDPListener.cpp):
case KADEMLIA2_REQ: DebugRecv(Kad2Req, ip, port); ProcessKademlia2Request(packetData, lenPacket, ip, port, senderKey); break;
KADEMLIA2_REQ正是我们的关键字查询消息的opcode,这也就是说消息处理的任务,会最终交给CKademliaUDPListener::ProcessKademlia2Request()函数来处理。
这里再来总结一下,关键字查询消息处理的任务是如何被一步步委托的:
首先是CClientUDPSocket/CEncryptedDatagramSocket接收到消息,产生一个ID_CLIENTUDPSOCKET_EVENT事件。消息被CamuleGuiApp::UDPSocketHandler()函数接收。
随后消息被委托给CMuleUDPSocket::OnReceive()函数处理。
随后消息再被委托给CClientUDPSocket::OnPacketReceived()函数处理。
随后消息又一次被委托给Kademlia::CKademlia::ProcessPacket()函数处理。
然后消息再次被委托,给CKademliaUDPListener::ProcessPacket()函数处理。
还没完,消息又最终被委托给CKademliaUDPListener::ProcessKademlia2Request()处理。
好艰难。
这里再来仔细地看一下CKademliaUDPListener::ProcessKademlia2Request()函数:
// KADEMLIA2_REQ // Used in Kad2.0 only void CKademliaUDPListener::ProcessKademlia2Request(const uint8_t *packetData, uint32_t lenPacket, uint32_t ip, uint16_t port, const CKadUDPKey& senderKey) { // Get target and type CMemFile bio(packetData, lenPacket); uint8_t type = bio.ReadUInt8(); type &= 0x1F; if (type == 0) { throw wxString(CFormat(wxT("***NOTE: Received wrong type (0x%02x) in %s")) % type % wxString::FromAscii(__FUNCTION__)); } // This is the target node trying to be found. CUInt128 target = bio.ReadUInt128(); // Convert Target to Distance as this is how we store contacts. CUInt128 distance(CKademlia::GetPrefs()->GetKadID()); distance.XOR(target); // This makes sure we are not mistaken identify. Some client may have fresh installed and have a new KadID. CUInt128 check = bio.ReadUInt128(); if (CKademlia::GetPrefs()->GetKadID() == check) { // Get required number closest to target ContactMap results; CKademlia::GetRoutingZone()->GetClosestTo(2, target, distance, type, &results); uint8_t count = (uint8_t)results.size(); // Write response // Max count is 32. size 817.. // 16 + 1 + 25(32) CMemFile packetdata(817); packetdata.WriteUInt128(target); packetdata.WriteUInt8(count); CContact *c; for (ContactMap::const_iterator it = results.begin(); it != results.end(); ++it) { c = it->second; packetdata.WriteUInt128(c->GetClientID()); packetdata.WriteUInt32(c->GetIPAddress()); packetdata.WriteUInt16(c->GetUDPPort()); packetdata.WriteUInt16(c->GetTCPPort()); packetdata.WriteUInt8(c->GetVersion()); //<- Kad Version inserted to allow backward compatibility. } DebugSendF(CFormat(wxT("Kad2Res (count=%u)")) % count, ip, port); SendPacket(packetdata, KADEMLIA2_RES, ip, port, senderKey, NULL); } }
发送请求的过程就是各种数据打包,而这个函数,则执行完全相反的操作,也就是解开数据包并处理。可以看到,此处解出了type,解出了target contact ID,解出了数据包发送的目的contact ID。然后对这些数据进行检验,看看数据包是不是发送给自己的。如果是,则从自己的联系人列表中找出距离target contact逻辑距离更近的联系人,并把他们的信息通过一个KADEMLIA2_RES消息发送给请求者。
KADEMLIA2_RES消息的具体处理过程可以参考Linux下电骡aMule Kademlia网络构建分析5 —— 资源的发布 一文。
如在Linux下电骡aMule Kademlia网络构建分析5 —— 资源的发布 一文所述,aMule会通过一个定时器来周期性的推动search的持续进行,也就是Kademlia::CKademlia::Process()->CSearchManager::JumpStart(),在amule-2.3.1/src/kademlia/kademlia/SearchManager.cpp中,可以看到这样的几个case:
while (next_it != m_searches.end()) { SearchMap::iterator current_it = next_it++; /* don't change this to a ++next_it! */ switch(current_it->second->GetSearchTypes()){ case CSearch::FILE: { if (current_it->second->m_created + SEARCHFILE_LIFETIME < now) { delete current_it->second; m_searches.erase(current_it); } else if (current_it->second->GetAnswers() > SEARCHFILE_TOTAL || current_it->second->m_created + SEARCHFILE_LIFETIME - SEC(20) < now) { current_it->second->PrepareToStop(); } else { current_it->second->JumpStart(); } break; } case CSearch::KEYWORD: { if (current_it->second->m_created + SEARCHKEYWORD_LIFETIME < now) { delete current_it->second; m_searches.erase(current_it); } else if (current_it->second->GetAnswers() > SEARCHKEYWORD_TOTAL || current_it->second->m_created + SEARCHKEYWORD_LIFETIME - SEC(20) < now) { current_it->second->PrepareToStop(); } else { current_it->second->JumpStart(); } break; } case CSearch::NOTES: { if (current_it->second->m_created + SEARCHNOTES_LIFETIME < now) { delete current_it->second; m_searches.erase(current_it); } else if (current_it->second->GetAnswers() > SEARCHNOTES_TOTAL || current_it->second->m_created + SEARCHNOTES_LIFETIME - SEC(20) < now) { current_it->second->PrepareToStop(); } else { current_it->second->JumpStart(); } break; }
它们的处理与资源发布时CSearchManager对于资源发布的Search的处理很相似,此处不再赘述,具体可以参考Linux下电骡aMule Kademlia网络构建分析5 —— 资源的发布 一文。最终要的执行路径都是会执行CSearch::JumpStart()。CSearch::JumpStart()最重要的执行路径会最终执行CSearch::StorePacket(),相关的更详细的分析也可以参考同一篇文章。
这里再来看一下CSearch::StorePacket()中与资源搜索相关的几个case(amule-2.3.1/src/kademlia/kademlia/Search.cpp):
// What kind of search are we doing? switch (m_type) { case FILE: { AddDebugLogLineN(logKadSearch, wxT("Search request type: File")); CMemFile searchTerms; searchTerms.WriteUInt128(m_target); if (from->GetVersion() >= 3) { // Find file we are storing info about. uint8_t fileid[16]; m_target.ToByteArray(fileid); CKnownFile *file = theApp->downloadqueue->GetFileByID(CMD4Hash(fileid)); if (file) { // Start position range (0x0 to 0x7FFF) searchTerms.WriteUInt16(0); searchTerms.WriteUInt64(file->GetFileSize()); DebugSend(Kad2SearchSourceReq, from->GetIPAddress(), from->GetUDPPort()); if (from->GetVersion() >= 6) { CUInt128 clientID = from->GetClientID(); CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_SOURCE_REQ, from->GetIPAddress(), from->GetUDPPort(), from->GetUDPKey(), &clientID); } else { CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_SOURCE_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); wxASSERT(from->GetUDPKey() == CKadUDPKey(0)); } } else { PrepareToStop(); break; } } else { searchTerms.WriteUInt8(1); DebugSendF(wxT("KadSearchReq(File)"), from->GetIPAddress(), from->GetUDPPort()); CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); } m_totalRequestAnswers++; break; } case KEYWORD: { AddDebugLogLineN(logKadSearch, wxT("Search request type: Keyword")); CMemFile searchTerms; searchTerms.WriteUInt128(m_target); if (from->GetVersion() >= 3) { if (m_searchTermsDataSize == 0) { // Start position range (0x0 to 0x7FFF) searchTerms.WriteUInt16(0); } else { // Start position range (0x8000 to 0xFFFF) searchTerms.WriteUInt16(0x8000); searchTerms.Write(m_searchTermsData, m_searchTermsDataSize); } DebugSend(Kad2SearchKeyReq, from->GetIPAddress(), from->GetUDPPort()); } else { if (m_searchTermsDataSize == 0) { searchTerms.WriteUInt8(0); // We send this extra byte to flag we handle large files. searchTerms.WriteUInt8(0); } else { // Set to 2 to flag we handle large files. searchTerms.WriteUInt8(2); searchTerms.Write(m_searchTermsData, m_searchTermsDataSize); } DebugSendF(wxT("KadSearchReq(Keyword)"), from->GetIPAddress(), from->GetUDPPort()); } if (from->GetVersion() >= 6) { CUInt128 clientID = from->GetClientID(); CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_KEY_REQ, from->GetIPAddress(), from->GetUDPPort(), from->GetUDPKey(), &clientID); } else if (from->GetVersion() >= 3) { CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_KEY_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); wxASSERT(from->GetUDPKey() == CKadUDPKey(0)); } else { CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); } m_totalRequestAnswers++; break; } case NOTES: { AddDebugLogLineN(logKadSearch, wxT("Search request type: Notes")); // Write complete packet. CMemFile searchTerms; searchTerms.WriteUInt128(m_target); if (from->GetVersion() >= 3) { // Find file we are storing info about. uint8_t fileid[16]; m_target.ToByteArray(fileid); CKnownFile *file = theApp->sharedfiles->GetFileByID(CMD4Hash(fileid)); if (file) { // Start position range (0x0 to 0x7FFF) searchTerms.WriteUInt64(file->GetFileSize()); DebugSend(Kad2SearchNotesReq, from->GetIPAddress(), from->GetUDPPort()); if (from->GetVersion() >= 6) { CUInt128 clientID = from->GetClientID(); CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_NOTES_REQ, from->GetIPAddress(), from->GetUDPPort(), from->GetUDPKey(), &clientID); } else { CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA2_SEARCH_NOTES_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); wxASSERT(from->GetUDPKey() == CKadUDPKey(0)); } } else { PrepareToStop(); break; } } else { searchTerms.WriteUInt128(CKademlia::GetPrefs()->GetKadID()); DebugSend(KadSearchNotesReq, from->GetIPAddress(), from->GetUDPPort()); CKademlia::GetUDPListener()->SendPacket(searchTerms, KADEMLIA_SEARCH_NOTES_REQ, from->GetIPAddress(), from->GetUDPPort(), 0, NULL); } m_totalRequestAnswers++; break; }这里我们逐个分析这三个case,
Case 1,搜索文件。
主要是将要搜索的文件相关的信息,比如target,文件的大小,请求的文件数据的偏移量等,打包进CMemFile searchTerms,并将searchTerms封装为一个KADEMLIA2_SEARCH_SOURCE_REQ消息,通过CKademliaUDPListener::SendPacket()函数发送出去。SendPacket()函数的具体执行过程,可以参考本篇前述内容。
Case 2,搜索关键字Keyword。
主要是将要搜索的文件相关的信息,比如关键字的hash值target等,打包进CMemFile searchTerms,并将searchTerms封装为一个KADEMLIA2_SEARCH_KEY_REQ消息,通过CKademliaUDPListener::SendPacket()函数发送出去。SendPacket()函数的具体执行过程,可以参考本篇前述内容。
Case 3,搜索FileNotes。
搜索FileNotes则主要是将打包好的数据封装为一个KADEMLIA2_SEARCH_NOTES_REQ消息通过CKademliaUDPListener::SendPacket()函数发送出去。其它则与前两种case相似,当然信息的格式会有一些差异。
我们称KADEMLIA2_SEARCH_SOURCE_REQ、KADEMLIA2_SEARCH_KEY_REQ和KADEMLIA2_SEARCH_NOTES_REQ等消息为具体的搜索请求。接收端在收到这些具体的搜索请求时又会如何处理呢?
消息的详细dispatch过程,如前文,此处不再赘述。我们直接来看CKademliaUDPListener::ProcessPacket()中对于这些消息的处理:
case KADEMLIA2_SEARCH_NOTES_REQ: DebugRecv(Kad2SearchNotesReq, ip, port); Process2SearchNotesRequest(packetData, lenPacket, ip, port, senderKey); break; case KADEMLIA2_SEARCH_KEY_REQ: DebugRecv(Kad2SearchKeyReq, ip, port); Process2SearchKeyRequest(packetData, lenPacket, ip, port, senderKey); break; case KADEMLIA2_SEARCH_SOURCE_REQ: DebugRecv(Kad2SearchSourceReq, ip, port); Process2SearchSourceRequest(packetData, lenPacket, ip, port, senderKey); break;
可以看到这里是把这些消息的处理分别委托给了Process2SearchNotesRequest(),Process2SearchKeyRequest()和Process2SearchSourceRequest()等几个函数。
// KADEMLIA2_SEARCH_KEY_REQ // Used in Kad2.0 only void CKademliaUDPListener::Process2SearchKeyRequest(const uint8_t *packetData, uint32_t lenPacket, uint32_t ip, uint16_t port, const CKadUDPKey& senderKey) { CMemFile bio(packetData, lenPacket); CUInt128 target = bio.ReadUInt128(); uint16_t startPosition = bio.ReadUInt16(); bool restrictive = ((startPosition & 0x8000) == 0x8000); startPosition &= 0x7FFF; SSearchTerm* pSearchTerms = NULL; if (restrictive) { pSearchTerms = CreateSearchExpressionTree(bio, 0); if (pSearchTerms == NULL) { throw wxString(wxT("Invalid search expression")); } } CKademlia::GetIndexed()->SendValidKeywordResult(target, pSearchTerms, ip, port, false, startPosition, senderKey); if (pSearchTerms) { Free(pSearchTerms); } } // KADEMLIA2_SEARCH_SOURCE_REQ // Used in Kad2.0 only void CKademliaUDPListener::Process2SearchSourceRequest(const uint8_t *packetData, uint32_t lenPacket, uint32_t ip, uint16_t port, const CKadUDPKey& senderKey) { CMemFile bio(packetData, lenPacket); CUInt128 target = bio.ReadUInt128(); uint16_t startPosition = (bio.ReadUInt16() & 0x7FFF); uint64_t fileSize = bio.ReadUInt64(); CKademlia::GetIndexed()->SendValidSourceResult(target, ip, port, startPosition, fileSize, senderKey); } 。。。。。。 // KADEMLIA2_SEARCH_NOTES_REQ // Used only by Kad2.0 void CKademliaUDPListener::Process2SearchNotesRequest(const uint8_t *packetData, uint32_t lenPacket, uint32_t ip, uint16_t port, const CKadUDPKey& senderKey) { CMemFile bio(packetData, lenPacket); CUInt128 target = bio.ReadUInt128(); uint64_t fileSize = bio.ReadUInt64(); CKademlia::GetIndexed()->SendValidNoteResult(target, ip, port, fileSize, senderKey); }
在这几个函数中,可以看到,都是从收到的数据包中解出一些信息,然后通过CIndexed的一些方法将请求端想要的数据发回去。在aMule中,CIndexed用来管理可以共享的文件的相关信息。具体来看CIndexed中这几个函数的处理过程(amule-2.3.1/src/kademlia/kademlia/Indexed.cpp):
void CIndexed::SendValidKeywordResult(const CUInt128& keyID, const SSearchTerm* pSearchTerms, uint32_t ip, uint16_t port, bool oldClient, uint16_t startPosition, const CKadUDPKey& senderKey) { KeyHash* currKeyHash = NULL; KeyHashMap::iterator itKeyHash = m_Keyword_map.find(keyID); if (itKeyHash != m_Keyword_map.end()) { currKeyHash = itKeyHash->second; CMemFile packetdata(1024 * 50); packetdata.WriteUInt128(Kademlia::CKademlia::GetPrefs()->GetKadID()); packetdata.WriteUInt128(keyID); packetdata.WriteUInt16(50); const uint16_t maxResults = 300; int count = 0 - startPosition; // we do 2 loops: In the first one we ignore all results which have a trustvalue below 1 // in the second one we then also consider those. That way we make sure our 300 max results are not full // of spam entries. We could also sort by trustvalue, but we would risk to only send popular files this way // on very hot keywords bool onlyTrusted = true; DEBUG_ONLY( uint32_t dbgResultsTrusted = 0; ) DEBUG_ONLY( uint32_t dbgResultsUntrusted = 0; ) do { for (CSourceKeyMap::iterator itSource = currKeyHash->m_Source_map.begin(); itSource != currKeyHash->m_Source_map.end(); ++itSource) { Source* currSource = itSource->second; for (CKadEntryPtrList::iterator itEntry = currSource->entryList.begin(); itEntry != currSource->entryList.end(); ++itEntry) { Kademlia::CKeyEntry* currName = static_cast<Kademlia::CKeyEntry*>(*itEntry); wxASSERT(currName->IsKeyEntry()); if ((onlyTrusted ^ (currName->GetTrustValue() < 1.0)) && (!pSearchTerms || currName->SearchTermsMatch(pSearchTerms))) { if (count < 0) { count++; } else if ((uint16_t)count < maxResults) { if (!oldClient || currName->m_uSize <= OLD_MAX_FILE_SIZE) { count++; #ifdef __DEBUG__ if (onlyTrusted) { dbgResultsTrusted++; } else { dbgResultsUntrusted++; } #endif packetdata.WriteUInt128(currName->m_uSourceID); currName->WriteTagListWithPublishInfo(&packetdata); if (count % 50 == 0) { DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); // Reset the packet, keeping the header (Kad id, key id, number of entries) packetdata.SetLength(16 + 16 + 2); } } } else { itSource = currKeyHash->m_Source_map.end(); --itSource; break; } } } } if (onlyTrusted && count < (int)maxResults) { onlyTrusted = false; } else { break; } } while (!onlyTrusted); AddDebugLogLineN(logKadIndex, CFormat(wxT("Kad keyword search result request: Sent %u trusted and %u untrusted results")) % dbgResultsTrusted % dbgResultsUntrusted); if (count > 0) { uint16_t countLeft = (uint16_t)count % 50; if (countLeft) { packetdata.Seek(16 + 16); packetdata.WriteUInt16(countLeft); DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); } } } Clean(); } void CIndexed::SendValidSourceResult(const CUInt128& keyID, uint32_t ip, uint16_t port, uint16_t startPosition, uint64_t fileSize, const CKadUDPKey& senderKey) { SrcHash* currSrcHash = NULL; SrcHashMap::iterator itSrcHash = m_Sources_map.find(keyID); if (itSrcHash != m_Sources_map.end()) { currSrcHash = itSrcHash->second; CMemFile packetdata(1024*50); packetdata.WriteUInt128(Kademlia::CKademlia::GetPrefs()->GetKadID()); packetdata.WriteUInt128(keyID); packetdata.WriteUInt16(50); uint16_t maxResults = 300; int count = 0 - startPosition; for (CKadSourcePtrList::iterator itSource = currSrcHash->m_Source_map.begin(); itSource != currSrcHash->m_Source_map.end(); ++itSource) { Source* currSource = *itSource; if (currSource->entryList.size()) { Kademlia::CEntry* currName = currSource->entryList.front(); if (count < 0) { count++; } else if (count < maxResults) { if (!fileSize || !currName->m_uSize || currName->m_uSize == fileSize) { packetdata.WriteUInt128(currName->m_uSourceID); currName->WriteTagList(&packetdata); count++; if (count % 50 == 0) { DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); // Reset the packet, keeping the header (Kad id, key id, number of entries) packetdata.SetLength(16 + 16 + 2); } } } else { break; } } } if (count > 0) { uint16_t countLeft = (uint16_t)count % 50; if (countLeft) { packetdata.Seek(16 + 16); packetdata.WriteUInt16(countLeft); DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); } } } Clean(); } void CIndexed::SendValidNoteResult(const CUInt128& keyID, uint32_t ip, uint16_t port, uint64_t fileSize, const CKadUDPKey& senderKey) { SrcHash* currNoteHash = NULL; SrcHashMap::iterator itNote = m_Notes_map.find(keyID); if (itNote != m_Notes_map.end()) { currNoteHash = itNote->second; CMemFile packetdata(1024*50); packetdata.WriteUInt128(Kademlia::CKademlia::GetPrefs()->GetKadID()); packetdata.WriteUInt128(keyID); packetdata.WriteUInt16(50); uint16_t maxResults = 150; uint16_t count = 0; for (CKadSourcePtrList::iterator itSource = currNoteHash->m_Source_map.begin(); itSource != currNoteHash->m_Source_map.end(); ++itSource ) { Source* currNote = *itSource; if (currNote->entryList.size()) { Kademlia::CEntry* currName = currNote->entryList.front(); if (count < maxResults) { if (!fileSize || !currName->m_uSize || fileSize == currName->m_uSize) { packetdata.WriteUInt128(currName->m_uSourceID); currName->WriteTagList(&packetdata); count++; if (count % 50 == 0) { DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); // Reset the packet, keeping the header (Kad id, key id, number of entries) packetdata.SetLength(16 + 16 + 2); } } } else { break; } } } uint16_t countLeft = count % 50; if (countLeft) { packetdata.Seek(16 + 16); packetdata.WriteUInt16(countLeft); DebugSend(Kad2SearchRes, ip, port); CKademlia::GetUDPListener()->SendPacket(packetdata, KADEMLIA2_SEARCH_RES, ip, port, senderKey, NULL); } } }
他们都是将请求的信息打包为CMemFile packetdata,然后包装为一个KADEMLIA2_SEARCH_RES消息,通过CKademliaUDPListener::SendPacket()函数发送出去。
球再次被踢会到搜索请求的发起端。来看CKademliaUDPListener::ProcessPacket()中对于KADEMLIA2_SEARCH_RES消息的处理:
case KADEMLIA2_SEARCH_RES: DebugRecv(Kad2SearchRes, ip, port); Process2SearchResponse(packetData, lenPacket, senderKey); break; 。。。。。。 void CKademliaUDPListener::ProcessSearchResponse(CMemFile& bio) { // What search does this relate to CUInt128 target = bio.ReadUInt128(); // How many results.. uint16_t count = bio.ReadUInt16(); while (count > 0) { // What is the answer CUInt128 answer = bio.ReadUInt128(); // Get info about answer // NOTE: this is the one and only place in Kad where we allow string conversion to local code page in // case we did not receive an UTF8 string. this is for backward compatibility for search results which are // supposed to be 'viewed' by user only and not feed into the Kad engine again! // If that tag list is once used for something else than for viewing, special care has to be taken for any // string conversion! CScopedContainer<TagPtrList> tags; bio.ReadTagPtrList(tags.get(), true/*bOptACP*/); CSearchManager::ProcessResult(target, answer, tags.get()); count--; } } 。。。。。。 // KADEMLIA2_SEARCH_RES // Used in Kad2.0 only void CKademliaUDPListener::Process2SearchResponse(const uint8_t *packetData, uint32_t lenPacket, const CKadUDPKey& WXUNUSED(senderKey)) { CMemFile bio(packetData, lenPacket); // Who sent this packet. bio.ReadUInt128(); ProcessSearchResponse(bio); }
搜索返回的信息会再转给CSearchManager::ProcessResult()处理(amule-2.3.1/src/kademlia/kademlia/SearchManager.cpp):
void CSearchManager::ProcessResult(const CUInt128& target, const CUInt128& answer, TagPtrList *info) { // We have results for a request for info. CSearch *s = NULL; SearchMap::const_iterator it = m_searches.find(target); if (it != m_searches.end()) { s = it->second; } // If this search was deleted before these results, delete contacts and abort, otherwise process them. if (s == NULL) { AddDebugLogLineN(logKadSearch, wxT("Search either never existed or receiving late results (CSearchManager::ProcessResult)")); } else { s->ProcessResult(answer, info); } }
搜索的结果最后还是要由具体的search自己来处理的:
void CSearch::ProcessResult(const CUInt128& answer, TagPtrList *info) { wxString type = wxT("Unknown"); switch (m_type) { case FILE: type = wxT("File"); ProcessResultFile(answer, info); break; case KEYWORD: type = wxT("Keyword"); ProcessResultKeyword(answer, info); break; case NOTES: type = wxT("Notes"); ProcessResultNotes(answer, info); break; } AddDebugLogLineN(logKadSearch, wxT("Got result (") + type + wxT(")")); }
整个搜索的过程大体如此。
Done。